diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..4c49bd78 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.env diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9cf157e9..710cfd21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,8 +7,13 @@ on: workflow_dispatch: jobs: - build: - runs-on: ubuntu-20.04 + docker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: ./bin/docker-test + ruby: + runs-on: ubuntu-latest env: BUNDLE_LOCAL: 1 steps: diff --git a/.github/workflows/deploy-fly.yml b/.github/workflows/deploy-fly.yml index d3b0ed41..ae915ab8 100644 --- a/.github/workflows/deploy-fly.yml +++ b/.github/workflows/deploy-fly.yml @@ -14,7 +14,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: + - uses: actions/checkout@v4 + - run: | + echo "RUBY_VERSION=$(cat .ruby-version)" >> $GITHUB_ENV - uses: dentarg/fly@main with: + build-args: "RUBY_VERSION=${{ env.RUBY_VERSION }}" fly-token: ${{ secrets.FLY_API_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..0072af9c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,58 @@ +ARG RUBY_VERSION +FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base + +# The app lives here +WORKDIR /app + +# Install base packages +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y \ + curl \ + postgresql-client + +# Set production environment +ENV BUNDLE_DEPLOYMENT="1" \ + BUNDLE_PATH="/usr/local/bundle" \ + BUNDLE_CACHE_PATH="/usr/local/bundle/cache" \ + BUNDLE_WITHOUT="development" + +# Throw-away build stage to reduce size of final image +FROM base as build + +# Install build packages +RUN apt-get install --no-install-recommends -y \ + build-essential \ + git \ + libpq-dev \ + pkg-config + +# Install application gems +COPY Gemfile Gemfile.lock .ruby-version ./ +COPY vendor/cache "${BUNDLE_CACHE_PATH}" +RUN bundle install --local + +# Copy application code +COPY . . + +# Final stage for app image +FROM base + +ARG APPUSER=app +ARG APPDIR=/app + +# Clean up installation packages to reduce image size +RUN rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Copy built artifacts: gems, application +COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" +COPY --from=build $APPDIR $APPDIR + +# Run and own only the runtime files as a non-root user for security +RUN mkdir -p log tmp +RUN groupadd --system --gid 1000 $APPUSER +RUN useradd $APPUSER --uid 1000 --gid 1000 --create-home --shell /bin/bash +RUN chown -R $APPUSER:$APPUSER log tmp +USER 1000:1000 + +# Start the server by default, this can be overwritten at runtime +CMD bin/start diff --git a/Gemfile b/Gemfile index 963cb4d7..6739e9ed 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ # frozen_string_literal: true source 'https://rubygems.org/' -ruby File.read('.ruby-version').chomp +ruby file: '.ruby-version' gem 'sequel' gem 'pg' @@ -25,11 +25,7 @@ gem 'warning' gem 'rake' gem 'rubocop', '~> 1.60.2', require: false gem 'dyno_metadata' - -group :development do - gem 'overman' - gem 'localhost' -end +gem 'localhost' group :test do gem 'climate_control' diff --git a/Gemfile.lock b/Gemfile.lock index 8f095ed4..89cbc6f1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -55,7 +55,6 @@ GEM base64 faraday (>= 1, < 3) sawyer (~> 0.9) - overman (0.87.3) parallel (1.24.0) parser (3.3.0.5) ast (~> 2.4.1) @@ -108,6 +107,7 @@ GEM faraday (>= 0.17.3, < 3) selma (0.2.2) rb_sys (~> 0.9) + selma (0.2.2-aarch64-linux) selma (0.2.2-arm64-darwin) selma (0.2.2-x86_64-linux) sentry-ruby (5.16.1) @@ -147,6 +147,7 @@ GEM zeitwerk (2.6.13) PLATFORMS + aarch64-linux arm64-darwin ruby x86_64-linux @@ -164,7 +165,6 @@ DEPENDENCIES m minitest octokit - overman pg puma rack-flash3 diff --git a/Procfile b/Procfile index c2c566e8..468d9907 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: bundle exec puma -C config/puma.rb +web: bundle exec puma --config config/puma.rb diff --git a/README.md b/README.md index ef2d5163..f5f447e8 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ These instructions assume you are using OS X. Install prerequisites brew install postgresql + gem install overman bundle install Ruby gems are vendored into `vendor/cache`, you should always check in the gems when changing gems. The caching is set up with [`bundle package --all`](https://bundler.io/man/bundle-package.1.html). @@ -24,13 +25,9 @@ Make sure PostgreSQL is running postgres -Get a copy of the production database - - rake db:pull - ### Start the app -In production, the script `bin/web_start` ([background](https://github.com/Starkast/wikimum/commit/acf57ec06ddb9ff3403acf56ababaa58f8cd3f43)) is used, but we avoid using that in the `Procfile` because the integration tests reads that command and needs to get the PID of Puma, not the script, in order to cleanly shutdown Puma. +In production, the script `bin/start` is used, but we avoid using that in the `Procfile` because the integration tests reads that command and needs to get the PID of Puma, not the script, in order to cleanly shutdown Puma. overman start @@ -59,7 +56,7 @@ MAINTENANCE_MODE=true ### Console - foreman run bundle exec racksh + overman run bundle exec racksh ### Tests @@ -90,7 +87,7 @@ GitHub Actions scan the code using [Brakeman](https://github.com/presidentbeef/b If you need to ignore a weakness reported, update `config/brakeman.ignore`. You can get the JSON needed by running Brakeman like this: ```bash -docker run -it --rm -v $(pwd):/app -w /app ruby:2.7.6 bash +docker run -it --rm -v $(pwd):/app -w /app ruby:$(cat .ruby-version) bash gem install brakeman brakeman --force --format json . ``` diff --git a/Rakefile b/Rakefile index 65f89d4c..ca437774 100644 --- a/Rakefile +++ b/Rakefile @@ -21,21 +21,6 @@ namespace(:test) do end namespace(:db) do - desc "Replace local database with production database" - task :pull do |t| - require "uri" - uri = URI.parse(ENV.fetch("DATABASE_URL", "postgres://localhost/wikimum")) - local_database = uri.path[1..-1] - - trap("INT") { exit } - - puts "Will remove local database '#{local_database}', press Enter to proceed, ^C to abort" - STDIN.gets - - system "dropdb #{local_database}" - system "heroku pg:pull --app wikimum DATABASE_URL #{local_database}" - end - desc "Run migrations" task :migrate, [:version] do |t, args| require 'sequel' diff --git a/bin/docker-build b/bin/docker-build new file mode 100755 index 00000000..bfdcc590 --- /dev/null +++ b/bin/docker-build @@ -0,0 +1,8 @@ +#!/bin/bash + +TAG=${TAG:-wikimum} + +set -e +set -x + +docker build . --build-arg RUBY_VERSION=$(cat .ruby-version) --tag $TAG "$@" diff --git a/bin/docker-reset-run b/bin/docker-reset-run new file mode 100755 index 00000000..50aa5eed --- /dev/null +++ b/bin/docker-reset-run @@ -0,0 +1,25 @@ +#!/bin/bash + +set -e +set -x + +docker compose up \ + --build \ + --always-recreate-deps \ + --force-recreate \ + --remove-orphans \ + --renew-anon-volumes \ + "$@" + +# this script is useful if you run into some strange problem with the docker setup + +# --always-recreate-deps Recreate dependent containers. Incompatible with --no-recreate. +# --force-recreate Recreate containers even if their configuration and image haven't changed. +# --remove-orphans Remove containers for services not defined in the Compose file. +#-V, --renew-anon-volumes Recreate anonymous volumes instead of retrieving data from the previous containers. + +# these flags will make use of more disk space +# check and clean up with +# +# docker system df +# docker system prune diff --git a/bin/docker-run b/bin/docker-run new file mode 100755 index 00000000..4d194a49 --- /dev/null +++ b/bin/docker-run @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e +set -x + +docker compose up \ + --build \ + "$@" + +# useful but mutually exclusive flags +# --detach +# --exit-code-from app diff --git a/bin/docker-test b/bin/docker-test new file mode 100755 index 00000000..3a30d675 --- /dev/null +++ b/bin/docker-test @@ -0,0 +1,24 @@ +#!/bin/bash + +set -x + +docker compose up \ + --build \ + --detach \ + --wait + +curl \ + --verbose \ + --silent \ + --output /dev/null \ + --fail-with-body \ + --retry 5 \ + --retry-all-errors \ + --retry-connrefused \ + 127.0.0.1:8080 + +result=$? + +docker compose stop + +exit $result diff --git a/bin/db_migrate b/bin/start similarity index 85% rename from bin/db_migrate rename to bin/start index a2b39f78..781e7b72 100755 --- a/bin/db_migrate +++ b/bin/start @@ -7,3 +7,4 @@ set -e set -u bundle exec rake db:migrate +bundle exec puma --config config/puma.rb diff --git a/bin/web_start b/bin/web_start deleted file mode 100755 index 15428796..00000000 --- a/bin/web_start +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -set -e -set -u - -bundle exec puma -C config/puma.rb diff --git a/default.env b/default.env new file mode 100644 index 00000000..8dfa715d --- /dev/null +++ b/default.env @@ -0,0 +1 @@ +SESSION_SECRET=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..042e8c22 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +version: "3.9" +services: + app: + build: + context: . + args: + - RUBY_VERSION + ports: + - "127.0.0.1:8080:3000" + env_file: + - ./default.env + environment: + DATABASE_URL: postgres://postgres:postgres@db/db + PORT: 3000 + depends_on: + db: + condition: service_healthy + db: + image: postgres + restart: always + # set shared memory limit when using docker-compose + # https://github.com/docker-library/postgres/issues/416 + shm_size: 128mb + environment: + POSTGRES_DB: db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + healthcheck: + test: ["CMD-SHELL", "pg_isready --dbname=db --username=postgres"] + interval: 2s + retries: 10 + start_period: 20s diff --git a/fly.toml b/fly.toml index 3ca0b2f2..1faf78e9 100644 --- a/fly.toml +++ b/fly.toml @@ -1,10 +1,9 @@ app = "wikimum" -[build] - builder = "heroku/buildpacks:20" - [processes] - web = "/bin/sh -c 'bin/db_migrate && bin/web_start'" + # Dockerfile controls this + # https://community.fly.io/t/multiple-process-dockerfile/13639 + web = "" [env] PRIMARY_REGION = "ams" diff --git a/vendor/cache/overman-0.87.3.gem b/vendor/cache/overman-0.87.3.gem deleted file mode 100644 index f593d3a3..00000000 Binary files a/vendor/cache/overman-0.87.3.gem and /dev/null differ diff --git a/vendor/cache/selma-0.2.2-aarch64-linux.gem b/vendor/cache/selma-0.2.2-aarch64-linux.gem new file mode 100644 index 00000000..73fafcfd Binary files /dev/null and b/vendor/cache/selma-0.2.2-aarch64-linux.gem differ