From f014cffe414247a9f31d1e93efb12a71379397b5 Mon Sep 17 00:00:00 2001 From: Philipp Fischbeck Date: Fri, 18 Aug 2023 15:55:33 +0200 Subject: [PATCH] Add Workflow for building and releasing Docker image --- .dockerignore | 39 ++++++++++++++++ .gitattributes | 9 ++++ .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 48 +++++++++++++++++++ .gitignore | 23 ++++++--- .ruby-version | 1 + Dockerfile | 77 +++++++++++++++++++++++++++++++ README.md | 4 +- bin/docker-entrypoint | 8 ++++ config/database.yml | 4 +- config/environment.rb | 14 ++++++ config/environments/production.rb | 2 +- 12 files changed, 220 insertions(+), 11 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitattributes create mode 100644 .github/workflows/release.yml create mode 100644 .ruby-version create mode 100644 Dockerfile create mode 100755 bin/docker-entrypoint diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9dc7e6a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,39 @@ +# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. + +# Ignore git directory. +/.git/ + +# Ignore bundler config. +/.bundle + +# Ignore all default key files. +/config/master.key +/config/credentials/*.key + +# Ignore all environment files. +/.env* +!/.env.example + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore storage (uploaded files in development and any SQLite databases). +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +# Ignore assets. +/node_modules/ +/app/assets/builds/* +!/app/assets/builds/.keep +/public/assets diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8dc4323 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored +config/credentials/*.yml.enc diff=rails_credentials +config/credentials.yml.enc diff=rails_credentials diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae7a61d..bc35c40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v3 with: - node-version: "18" + node-version: "20" - name: Install dependencies run: bundle install --without production - name: Run tests diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e1996fc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,48 @@ +# +name: Create and publish a Docker image + +# Configures this workflow to run every time a change is pushed to the branch called `release`. +on: + push: + branches: ["release"] + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + build-and-push-image: + runs-on: ubuntu-latest + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: write + # + steps: + - name: Checkout repository + uses: actions/checkout@v3 + # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. + # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Build and push Docker image + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index a12ff0f..93412f1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,13 +12,24 @@ /db/*.sqlite3-journal # Ignore all logfiles and tempfiles. -/log/*.log -/tmp +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep -# Ignore coverage data generated by coveralls -/coverage -# Ignore encrypted secrets key file. -config/secrets.yml.key +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore storage (uploaded files in development and any SQLite databases). +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets # Ignore master key for decrypting credentials and more. /config/master.key diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..9e79f6c --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +ruby-3.2.2 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5c1556b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,77 @@ +# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile +ARG RUBY_VERSION=3.2.2 +FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base + +# Rails app lives here +WORKDIR /rails + +# Set production environment +ENV RAILS_ENV="production" \ + BUNDLE_DEPLOYMENT="1" \ + BUNDLE_PATH="/usr/local/bundle" \ + BUNDLE_WITHOUT="development" + + +# Throw-away build stage to reduce size of final image +FROM base as build + +# Install packages needed to build gems and node modules +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y build-essential git libvips pkg-config curl node-gyp default-libmysqlclient-dev + +# Install JavaScript dependencies +ARG NODE_VERSION=20.5.0 +ARG YARN_VERSION=1.22.19 +ENV PATH=/usr/local/node/bin:$PATH +RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ + /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ + npm install -g yarn@$YARN_VERSION && \ + rm -rf /tmp/node-build-master + + +# Install application gems +COPY Gemfile Gemfile.lock ./ +RUN bundle install && \ + rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ + bundle exec bootsnap precompile --gemfile + +# Install node modules +#COPY package.json yarn.lock ./ +#RUN yarn install --frozen-lockfile + +# Copy application code +COPY . . + +# Precompile bootsnap code for faster boot times +RUN bundle exec bootsnap precompile app/ lib/ + +# 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 +LABEL org.opencontainers.image.source=https://github.com/projekteuler/projekteuler +LABEL org.opencontainers.image.description="Projekteuler production image" +LABEL org.opencontainers.image.licenses=MIT + +# Install packages needed for deployment +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y curl node-gyp default-mysql-client libvips && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Copy built artifacts: gems, application +COPY --from=build /usr/local/bundle /usr/local/bundle +COPY --from=build /rails /rails + +# Run and own only the runtime files as a non-root user for security +RUN useradd rails --create-home --shell /bin/bash && \ + chown -R rails:rails db log tmp +USER rails:rails + +# Entrypoint prepares the database. +ENTRYPOINT ["/rails/bin/docker-entrypoint"] + +# Start the server by default, this can be overwritten at runtime +EXPOSE 3000 +CMD ["./bin/rails", "server"] diff --git a/README.md b/README.md index 14255d2..1c866a7 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ It allows visitors to view and suggest German translations of the maths puzzles 2. Make sure [NodeJS >= 18](https://nodejs.org) is installed. 3. Navigate inside the repository folder. 4. Use bundler to install all required gems. - $ bundle install + $ bundle install 5. Setup the database: $ bin/rails db:setup @@ -47,6 +47,8 @@ Do you want to implement this improvement yourself? Follow these steps: - `DATABASE_NAME`: The name of the MySQL database - `DATABASE_USERNAME`: The username for the MYSQL database - `DATABASE_PASSWORD`: The password for the MYSQL database + - `DATABASE_HOST`: The host of the MYSQL database + - `DATABASE_PORT`: The port of the MYSQL database - `GITHUB_CLIENT_ID`: The client ID of your GitHub OAuth App (that you will need to create) - `GITHUB_CLIENT_SECRET`: The client secret of the GitHub OAuth App 6. Install all required gems with `bundle install`. diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint new file mode 100755 index 0000000..dffd4ba --- /dev/null +++ b/bin/docker-entrypoint @@ -0,0 +1,8 @@ +#!/bin/bash -e + +# If running the rails server then create or migrate existing database +if [ "${*}" == "./bin/rails server" ]; then + ./bin/rails db:prepare +fi + +exec "${@}" diff --git a/config/database.yml b/config/database.yml index 0b7e86e..a4ea782 100644 --- a/config/database.yml +++ b/config/database.yml @@ -27,5 +27,5 @@ production: database: <%= ENV['DATABASE_NAME'] %> username: <%= ENV['DATABASE_USERNAME'] %> password: <%= ENV['DATABASE_PASSWORD'] %> - host: 127.0.0.1 - port: 3306 + host: <%= ENV['DATABASE_HOST'] %> + port: <%= ENV['DATABASE_PORT'] %> diff --git a/config/environment.rb b/config/environment.rb index cac5315..bc913ec 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,5 +1,19 @@ # Load the Rails application. require_relative "application" +# Replace when upgrading to Rails 7.1 +# See https://github.com/rails/rails/issues/32947#issuecomment-1356391185 +class Rails::Application + def secret_key_base + if Rails.env.development? || Rails.env.test? || ENV["SECRET_KEY_BASE_DUMMY"] + secrets.secret_key_base ||= generate_development_secret + else + validate_secret_key_base( + ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base + ) + end + end +end + # Initialize the Rails application. Rails.application.initialize! diff --git a/config/environments/production.rb b/config/environments/production.rb index dae002c..6781f53 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -18,7 +18,7 @@ Rails.application.configure do # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). - config.require_master_key = true + config.require_master_key = false # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this.