Skip to content

Commit

Permalink
Add pre-configured deploy to Fly (#317)
Browse files Browse the repository at this point in the history
  • Loading branch information
krschacht authored May 2, 2024
1 parent d756f54 commit 29191da
Show file tree
Hide file tree
Showing 15 changed files with 271 additions and 49 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/docker-build-development.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Docker Development Build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
docker-build:
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}-test
cancel-in-progress: true
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
push: false
target: development
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Docker Container Build
name: Docker Fly Build
on:
push:
branches: [ "main" ]
Expand All @@ -19,3 +19,4 @@ jobs:
uses: docker/build-push-action@v5
with:
push: false
target: fly-production
22 changes: 22 additions & 0 deletions .github/workflows/docker-build-render.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Docker Render Build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
docker-build:
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}-test
cancel-in-progress: true
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
push: false
target: render-production
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# Ignore bundler config.
/.bundle
/.tool-versions
/node_modules

# Ignore all environment files (except templates).
Expand Down
126 changes: 118 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,26 +1,131 @@
FROM ruby:3.2.3-alpine AS base
# syntax = docker/dockerfile:1

### START of FLY ####

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.2.3
FROM quay.io/evl.ms/fullstaq-ruby:${RUBY_VERSION}-jemalloc-slim as base-for-fly

LABEL fly_launch_runtime="rails"

# Rails app lives here
WORKDIR /rails

# Set production environment
ENV BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development:test" \
RAILS_ENV="production"

# Update gems and bundler
RUN gem update --system --no-document && \
gem install -N bundler


# Throw-away build stage to reduce size of final image
FROM base-for-fly as build

# Install packages needed to build gems
RUN --mount=type=cache,id=dev-apt-cache,sharing=locked,target=/var/cache/apt \
--mount=type=cache,id=dev-apt-lib,sharing=locked,target=/var/lib/apt \
apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential libpq-dev libvips libyaml-dev

# Install application gems
COPY --link Gemfile Gemfile.lock .ruby-version ./
RUN --mount=type=cache,id=bld-gem-cache,sharing=locked,target=/srv/vendor \
bundle config set app_config .bundle && \
bundle config set path /srv/vendor && \
bundle install && \
bundle exec bootsnap precompile --gemfile && \
bundle clean && \
mkdir -p vendor && \
bundle config set path vendor && \
cp -ar /srv/vendor .

# Copy application code
COPY --link . .

# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/

# Adjust binfiles to set current working directory
RUN grep -l '#!/usr/bin/env ruby' /rails/bin/* | xargs sed -i '/^#!/aDir.chdir File.expand_path("..", __dir__)'

# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile


# Final stage for app image
FROM base-for-fly AS fly-production

# Install packages needed for deployment
RUN --mount=type=cache,id=dev-apt-cache,sharing=locked,target=/var/cache/apt \
--mount=type=cache,id=dev-apt-lib,sharing=locked,target=/var/lib/apt \
apt-get update -qq && \
apt-get install --no-install-recommends -y curl imagemagick libvips postgresql-client

# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails

# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
chown -R 1000:1000 db log storage tmp
USER 1000:1000

# Deployment options
ENV RUBY_YJIT_ENABLE="1"

# Entrypoint sets up the container.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000

#### END of FLY ####

#### START of DEV ####

# RUBY_VERSION is the only thing used from anything above
FROM ruby:${RUBY_VERSION}-alpine AS development

RUN apk add --no-cache git build-base postgresql-dev curl-dev gcompat tzdata vips-dev imagemagick

ENV BUNDLE_CACHE=/tmp/bundle \
BUNDLE_JOBS=2 \
PORT=3000

WORKDIR /app
COPY Gemfile Gemfile.lock .tool-versions ./
WORKDIR /rails
COPY Gemfile Gemfile.lock .ruby-version ./

RUN --mount=type=cache,id=gems,target=/tmp/bundle \
bundle install

ENTRYPOINT ["/app/bin/docker-entrypoint"]
RUN apk add --no-cache postgresql-client

ENTRYPOINT ["/rails/bin/docker-entrypoint"]
CMD ["./bin/rails", "server", "-b", "0.0.0.0"]

FROM base as development
#### END of DEV ####

RUN apk add --no-cache postgresql-client
#### START of RENDER ####
# Render must be last because render.yml cannot specify a build target so it default to the last one
# RUBY_VERSION is the only thing used from anything above
FROM ruby:${RUBY_VERSION}-alpine AS render-production

RUN apk add --no-cache git build-base postgresql-dev curl-dev gcompat tzdata vips-dev imagemagick

ENV BUNDLE_CACHE=/tmp/bundle \
BUNDLE_JOBS=2 \
PORT=3000

WORKDIR /rails
COPY Gemfile Gemfile.lock .ruby-version ./

FROM base AS deployment
RUN --mount=type=cache,id=gems,target=/tmp/bundle \
bundle install

ENV BUNDLE_DEPLOYMENT=1 \
BUNDLE_WITHOUT=development \
Expand All @@ -40,6 +145,11 @@ RUN mkdir -p log tmp bin

RUN adduser rails -D -h /rails -s /bin/sh && \
chown -R rails:rails db log tmp bin && \
chmod 755 /app/bin/docker-entrypoint
chmod 755 /rails/bin/docker-entrypoint

USER rails:rails

ENTRYPOINT ["/rails/bin/docker-entrypoint"]
CMD ["./bin/rails", "server", "-b", "0.0.0.0"]

#### END of RENDER ####
4 changes: 3 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

source "https://rubygems.org"

ruby File.readlines(File.join(__dir__ , '.tool-versions')).select{|l| l =~ /ruby/}.first.strip.split.last
ruby file: ".ruby-version"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.1.3"
Expand Down Expand Up @@ -83,6 +83,8 @@ group :development do

# https://github.com/kirillplatonov/hotwire-livereload
gem "hotwire-livereload", "~> 1.3"

gem "dockerfile-rails", ">= 1.6"
end

group :test do
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ GEM
debug (1.8.0)
irb (>= 1.5.0)
reline (>= 0.3.1)
dockerfile-rails (1.6.10)
rails (>= 3.0.0)
drb (2.2.1)
erubi (1.12.0)
event_stream_parser (1.0.0)
Expand Down Expand Up @@ -369,6 +371,7 @@ DEPENDENCIES
byebug
capybara
debug
dockerfile-rails (>= 1.6)
hotwire-livereload (~> 1.3)
htmlbeautifier
image_processing (~> 1.2)
Expand Down
41 changes: 37 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ ChatGPT uses your private conversations history to train its models. [OpenAI dis

# Table of Contents

- [Setup the app](#setup-the-app)
- [Deploy the app on Render](#deploy-the-app-on-render)
- [Deploy the app on Fly.io](#deploy-the-app-on-fly)
- [Contribute as a developer](#contribute-as-a-developer)
- [Understanding the Docker configuration](#understanding-the-docker-configuration)
- [Changelog](#changelog)

# Setup the app

You can deploy a full version of HostedGPT to the hosting service, Render, for free. This free app works for 90 days and then the database will stop working. You will need to upgrade to a paid version of the database which is $7 / month. Alternatively, you can also run it off your local computer. Jump down to the [Developer Instructions](#contribute-as-a-developer) if you want to run it locally.
# Deploy the app on Render

For the easiest way to get started, deploy a full version of HostedGPT to the hosting service, Render, for free. This free app works for 90 days and then the database will stop working. You will need to upgrade to a paid version of the database which is $7 / month. Alternatively, you can also run it off your local computer. Jump down to the [Developer Instructions](#contribute-as-a-developer) if you want to run it locally.

1. Click Fork > Create New Fork at the top of this repository
2. Create an account on Render.com and login. If you are new to Render, you may be prompted to add a credit card to your account. However, you will be on their free plan by default unless you choose to upgrade.
Expand All @@ -52,14 +55,32 @@ You can deploy a full version of HostedGPT to the hosting service, Render, for f

## Troubleshooting Render

1. If you encountered an error while waiting for the services to be deployed on Render, click **Dashboard** at the top of the Render screen and click the Service that failed.
If you encountered an error while waiting for the services to be deployed on Render:

1. Login to your account on Render.com and click **Dashboard** at the top then click the Service that failed.
2. It should take you to the Events section and the top event should explain the error. It will probably contain a link to click to the **deploy logs**
3. Scroll back up through the logs and find any instances of errors. [Start a new discussion](https://github.com/allyourbot/hostedgpt/discussions/new?category=general) and share details.
4. When you are ready to try Render again, it's best to do the following:
5. First, ensure your repo is caught up. Open your fork in github, click the Sync Fork button so that any bug fixes are pulled in.
6. Second, in Render navigate to the Dashboard, Bluebrint, and Env Groups and delete any details associated with **hostedgpt**
7. Now you can go back to your repo and click **Deploy to Render**


# Deploy the app on Fly

The hosting service Fly.io does not have one-click hosting like Render and it does not have a free tier that HostedGPT can fit within, that's why Render is our default recommendation. But Fly is also great and we have created a configuration to make it easy for you to deploy there.

1. You must first install the Fly command-line tool [view instructions](https://fly.io/docs/hands-on/install-flyctl/)
3. Next run `fly launch` and say `Yes` to copy the existing fly.toml but note that it will generate the wrong settings
4. **The settings it shows are INCORRECT** so tell it you want to make changes
5. When it opens your browser, change the Database to `Fly Postgres` and a name such as `hostedgpt-db` and you can set the configuration to `Development`
6. Also in the browser, change the Redis to `Upstash for Redis`
7. Click `Confirm Settings` and close the browser.
8. The app will do a bunch of build steps and attempt to deploy the app but **it will fail with an error message about "Missing `secret_key_base`"**. When you get back to the command prompt, first scroll through the output and save the Postgres username & password somewhere as you'll never be able to see those again.
9. Next run `bin/rails db:setup_encryption[true]`. This will initialize some private keys for your app and send them to Fly. (This can't be done until your app exists, which doesn't happen until you attempt your first deploy, that's why we intentionally hit that error.)
10. Finally run `fly deploy`


# Contribute as a developer

We welcome contributors! After you get your developoment environment setup, review the list of Issues. We organize the issues into Milestones and are currently working on v0.7. [View 0.7 Milestone](https://github.com/allyourbot/hostedgpt/milestone/6). Look for any issues tagged with **Good first issue** and add a comment so we know you're working on it.
Expand Down Expand Up @@ -96,6 +117,18 @@ HostedGPT requires these services to be running:

Every time you pull new changes down, kill `bin/dev` and then re-run it. This will ensure your local app picks up changes to Gemfile and migrations.


# Understanding the Docker configuration

The `Dockerfile` is set up to support three distinct situations: development, deploying to Render, and deploying to Fly. Each of these are completely separate targets which don't share any steps, they are simply in the same Dockerfile.

The `docker-compose.yml` is solely for development. It references the `development` build target.

The `render.yml` specifies details of the Render production environment. Note that Render does not support specifying a build target within this file, it simply defaults to the last target with the Dockerfile so the order of the sections within there matter.

The `fly.toml` specifies details of the Fly production environment. It references the `fly-production` build target. The Fly section of the Dockerfile was generated using the dockerfile-rails generator. This is Fly's recommendation and it produces a reasonable production-ready Dockerfile. Edits to this _top section_ of the file have been kept very minimal, on purpose, because it's intended to be updated using the generator. When it was originally generated it saved all the configuration parameters into `config/dockerfile.yml`. When you run `bin/rails generate dockerfile` it will read all these configurations and attempt to re-generate the Dockerfile. You can try this, it will warn you that it's going to overwrite, and press `d` to see the diff of what changes it will make. There should be no functional changes above the line `#### END of FLY ####`. Imagine you wanted to use this generator to change the app to use MySQL ((view all generator options)[https://github.com/fly-apps/dockerfile-rails]). You could run `bin/rails generate dockerfile --mysql` and it would update your Gemfile, automatically run bundle install to install any gem changes, and then it will attempt to update Dockerfile where you can again press `d`. Inspect the diff of any changes above the line `#### END of FLY ####` and manually apply those changes. Similarly, view the diff for dockerignore and docker-entrypoint, although none of those changes should be necessary. When you get to `fly.toml` you will want to view that diff closely and manually apply those changes. At the end it will update config/dockerfile.yml to record the new configuration of the Dockerfile. In this way, you can continue to use the generator to keep the Dockerfile updated (as recommended by Fly) while not breaking the dev or Render setup.


# Changelog

(Top features being developed for v0.7: voice support, Gemini Pro, pin conversations)
Expand Down
8 changes: 4 additions & 4 deletions bin/docker-entrypoint
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#!/bin/sh -e

if [ "$FLY_APP_NAME" != "" ] && [ "$RAILS_ENV" == "production" ]; then
if [ "$FLY_APP_NAME" != "" ] && [ "$RAILS_ENV" = "production" ]; then
echo "Running on Fly which runs a release command. Skipping docker-entrypoint."
else
if echo "${1}" | grep -q "rails$" && [ "${2}" == "server" ]; then
if echo "${1}" | grep -q "rails$" && [ "${2}" = "server" ]; then
rm -f ./tmp/pids/server.pid 2>/dev/null
echo "Running db:prepare"
./bin/rails db:prepare
elif echo "${1}" | grep -q "rails$" && [ "${2}" == "test" ]; then
elif echo "${1}" | grep -q "rails$" && [ "${2}" = "test" ]; then
export RAILS_ENV=test
echo "Running db:prepare"
./bin/rails db:prepare
Expand All @@ -16,7 +16,7 @@ else
fi
fi

if [ "${1}" == "psql" ]; then
if [ "${1}" = "psql" ]; then
echo "Running psql to connect to development database"
exec psql -d "$DATABASE_URL"
fi
Expand Down
14 changes: 14 additions & 0 deletions config/dockerfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# generated by dockerfile-rails

---
options:
bin-cd: true
cache: true
fullstaq: true
jemalloc: true
label:
fly_launch_runtime: rails
postgresql: true
prepare: false
redis: true
yjit: true
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ services:
- DATABASE_URL=postgres://app:secret@postgres/app_development
ports: ["3000:3000"]
volumes:
- .:/app
- /app/tmp
- .:/rails
- /rails/tmp

worker:
<<: *base
Expand Down
Loading

0 comments on commit 29191da

Please sign in to comment.