├── .github ├── dependabot.yml └── workflows │ ├── automerge.yml │ └── ci.yml ├── .ruby-version ├── LICENSE ├── README.md ├── builder ├── .dockerignore ├── .yarnclean ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── package.json └── yarn.lock └── final └── Dockerfile /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: bundler 9 | directory: '/builder' 10 | schedule: 11 | interval: daily 12 | time: '01:00' 13 | timezone: Europe/Berlin 14 | open-pull-requests-limit: 10 15 | versioning-strategy: lockfile-only 16 | allow: 17 | - dependency-type: direct 18 | - dependency-type: indirect 19 | labels: 20 | - 'dependencies' 21 | - 'ruby' 22 | groups: 23 | rails: 24 | patterns: 25 | - 'actioncable' 26 | - 'actionmailbox' 27 | - 'actionmailer' 28 | - 'actionpack' 29 | - 'actiontext' 30 | - 'actionview' 31 | - 'activejob' 32 | - 'activemodel' 33 | - 'activerecord' 34 | - 'activestorage' 35 | - 'activesupport' 36 | - 'rails' 37 | - 'railties' 38 | - 'globalid' 39 | - 'i18n' 40 | - 'mail' 41 | - 'rack' 42 | - 'rackup' 43 | turbo: 44 | patterns: 45 | - 'turbo-rails' 46 | - 'stimulus-rails' 47 | 48 | - package-ecosystem: 'github-actions' 49 | directory: '/' 50 | schedule: 51 | interval: 'daily' 52 | time: '01:00' 53 | timezone: Europe/Berlin 54 | labels: 55 | - 'dependencies' 56 | - 'gh-action' 57 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request_target 3 | 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v2.4.0 16 | with: 17 | github-token: '${{ secrets.PAT }}' 18 | 19 | - name: Enable auto-merge for Dependabot PRs 20 | if: ${{ steps.metadata.outputs.update-type != 'version-update:semver-major' }} 21 | run: gh pr merge --auto --squash "$PR_URL" 22 | env: 23 | PR_URL: ${{github.event.pull_request.html_url}} 24 | GITHUB_TOKEN: ${{secrets.PAT}} 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build images 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | workflow_dispatch: 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | env: 16 | TAG_NAME: 3.4.4-alpine 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Set up Docker Buildx 24 | uses: docker/setup-buildx-action@v3 25 | 26 | - name: Set up QEMU 27 | uses: docker/setup-qemu-action@v3 28 | with: 29 | platforms: linux/amd64,linux/arm64 30 | 31 | - name: Login to ghcr.io 32 | uses: docker/login-action@v3 33 | if: github.ref == 'refs/heads/main' 34 | with: 35 | registry: ghcr.io 36 | username: ${{ github.repository_owner }} 37 | password: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: Build and push BUILDER image 40 | uses: docker/build-push-action@v6 41 | with: 42 | context: '{{defaultContext}}:builder' 43 | platforms: linux/amd64,linux/arm64 44 | provenance: false 45 | push: ${{ github.ref == 'refs/heads/main' }} 46 | tags: | 47 | ghcr.io/${{ github.repository_owner }}/rails-base-builder:${{ env.TAG_NAME }} 48 | ghcr.io/${{ github.repository_owner }}/rails-base-builder:latest 49 | cache-from: type=gha 50 | cache-to: type=gha,mode=max 51 | 52 | - name: Build and push FINAL image 53 | uses: docker/build-push-action@v6 54 | with: 55 | context: '{{defaultContext}}:final' 56 | platforms: linux/amd64,linux/arm64 57 | provenance: false 58 | push: ${{ github.ref == 'refs/heads/main' }} 59 | tags: | 60 | ghcr.io/${{ github.repository_owner }}/rails-base-final:${{ env.TAG_NAME }} 61 | ghcr.io/${{ github.repository_owner }}/rails-base-final:latest 62 | cache-from: type=gha 63 | cache-to: type=gha,mode=max 64 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4.4 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2025 Georg Ledermann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build images](https://github.com/ledermann/docker-rails-base/actions/workflows/ci.yml/badge.svg)](https://github.com/ledermann/docker-rails-base/actions/workflows/ci.yml) 2 | 3 | # DockerRailsBase 4 | 5 | Building Docker images usually takes a long time. This repo contains base images with preinstalled dependencies for [Ruby on Rails](https://rubyonrails.org/), so building a production image will be **2-3 times faster**. 6 | 7 | ## What? 8 | 9 | When using the official Ruby image, building a Docker image for a typical Rails application requires lots of time for installing dependencies - mainly OS packages, Ruby gems, Ruby gems with native extensions (Nokogiri etc.) and Node modules. This is required every time the app needs to be deployed to production. 10 | 11 | I was looking for a way to reduce this time, so I created base images that contain most of the dependencies used in my applications. 12 | 13 | And while I'm at it, I also moved as much as possible from the app-specific Dockerfile into the base image by using [ONBUILD](https://docs.docker.com/engine/reference/builder/#onbuild) triggers. This makes the Dockerfile in my apps small and simple. 14 | 15 | ## Performance 16 | 17 | I compared building times using a typical Rails application. This is the result on my local machine: 18 | 19 | - Based on official Ruby image: **4:50 min** 20 | - Based on DockerRailsBase: **1:57 min** 21 | 22 | As you can see, using DockerRailsBase is more than **2 times faster** compared to the official Ruby image. It saves nearly **3min** on every build. 23 | 24 | Note: Before I started timing, the base image was not available on my machine, so it was downloaded first, which took some time. If the base image is already available, the building time is only 1:18min (**3 times faster**). 25 | 26 | # Requirements 27 | 28 | This repo is based on the following assumptions: 29 | 30 | - Your Docker host is compatible with [Alpine Linux 3.22](https://www.alpinelinux.org/posts/Alpine-3.22.0-released.html), which requires Docker 20.10.0 or later 31 | - Your app is compatible with [Ruby 3.4 for Alpine Linux](https://github.com/docker-library/ruby/blob/master/3.4/alpine3.22/Dockerfile) 32 | - Your app uses Ruby on Rails 7.1 or later (including Rails 8.0) 33 | - Your app uses PostgreSQL, SQLite or MySQL/MariaDB 34 | - Your app installs Node modules with [Yarn](https://yarnpkg.com/) 35 | - Your app bundles JavaScript with `rails assets:precompile`. This works with [Vite Ruby](https://github.com/ElMassimo/vite_ruby), [Webpacker](https://github.com/rails/webpacker), [Asset pipeline (Sprockets)](https://github.com/rails/sprockets-rails) and others. 36 | 37 | If your project differs from this, I suggest to fork this project and create your own base image. 38 | 39 | ## How? 40 | 41 | It uses [multi-stage building](https://docs.docker.com/develop/develop-images/multistage-build/) to build a very small production image. There are two Dockerfiles in this repo, one for the first stage (called `builder`) and one for the resulting stage (called `final`). 42 | 43 | ### Builder stage 44 | 45 | The `builder` stage installs Ruby gems and Node modules. It also includes Git, Node.js and some build tools - all we need to compile assets. 46 | 47 | - Based on [ruby:3.4.4-alpine](https://github.com/docker-library/ruby/blob/master/3.4/alpine3.22/Dockerfile) 48 | - Adds packages needed for installing gems and compiling assets: Git, Node.js, Yarn, PostgreSQL client and build tools 49 | - Adds some default Ruby gems (Rails 8.0 etc., see [Gemfile](./builder/Gemfile)) 50 | - Via ONBUILD triggers it installs missing gems and Node modules, then compiles the assets 51 | 52 | See [builder/Dockerfile](./builder/Dockerfile) 53 | 54 | ### Final stage 55 | 56 | The `final` stage builds the production image, which includes just the bare minimum. 57 | 58 | - Based on [ruby:3.4.4-alpine](https://github.com/docker-library/ruby/blob/master/3.4/alpine3.22/Dockerfile) 59 | - Adds packages needed for production: postgresql-client, tzdata, file 60 | - Via ONBUILD triggers it mainly copies the app and gems from the `builder` stage 61 | 62 | See [final/Dockerfile](./final/Dockerfile) 63 | 64 | ### Staying up-to-date 65 | 66 | Using [Dependabot](https://dependabot.com/), every updated Ruby gem results in an updated image. 67 | 68 | ### How to use for your Rails application 69 | 70 | #### Building the Docker image 71 | 72 | Add this `Dockerfile` to your application: 73 | 74 | ```Dockerfile 75 | FROM ghcr.io/ledermann/rails-base-builder:3.4.4-alpine AS builder 76 | FROM ghcr.io/ledermann/rails-base-final:3.4.4-alpine 77 | USER app 78 | # Optional: Enable YJIT 79 | # ENV RUBY_YJIT_ENABLE=1 80 | CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"] 81 | ``` 82 | 83 | Yes, this is the complete `Dockerfile` of your Rails app. It's so simple because the work is done by ONBUILD triggers. 84 | 85 | Now build the image: 86 | 87 | ```bash 88 | $ docker build . 89 | ``` 90 | 91 | #### Building the Docker image with BuildKit 92 | 93 | ``` 94 | docker buildx build . 95 | ``` 96 | 97 | You can use private npm/Yarn packages by mounting the config file: 98 | 99 | ``` 100 | docker buildx build --secret id=npmrc,src=$HOME/.npmrc . 101 | ``` 102 | 103 | or 104 | 105 | ``` 106 | docker buildx build --secret id=yarnrc,src=$HOME/.yarnrc.yml . 107 | ``` 108 | 109 | In a similar way you can provide a configuration file for Bundler: 110 | 111 | ``` 112 | docker buildx build --secret id=bundleconfig,src=$HOME/.bundle/config . 113 | ``` 114 | 115 | #### Continuous integration (CI) 116 | 117 | Example to build the application's image with GitHub Actions and push it to the GitHub Container Registry: 118 | 119 | ```yaml 120 | deploy: 121 | runs-on: ubuntu-latest 122 | 123 | steps: 124 | - uses: actions/checkout@v4 125 | with: 126 | fetch-depth: 0 127 | 128 | - name: Fetch tag annotations 129 | # https://github.com/actions/checkout/issues/290 130 | run: git fetch --tags --force 131 | 132 | - name: Login to GitHub Container Registry 133 | uses: docker/login-action@v3 134 | with: 135 | registry: ghcr.io 136 | username: ${{ github.repository_owner }} 137 | password: ${{ secrets.GITHUB_TOKEN }} 138 | 139 | - name: Build the image 140 | run: | 141 | export COMMIT_TIME=$(git show -s --format=%cI ${GITHUB_SHA}) 142 | export COMMIT_SHA=${GITHUB_SHA} 143 | export COMMIT_VERSION=$(git describe) 144 | export COMMIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 145 | docker buildx build --build-arg COMMIT_TIME --build-arg COMMIT_SHA --build-arg COMMIT_VERSION --build-arg COMMIT_BRANCH -t ghcr.io/user/repo:latest . 146 | 147 | - name: Push the image 148 | run: docker push ghcr.io/user/repo:latest 149 | ``` 150 | 151 | ## Available Docker images 152 | 153 | Both Docker images (`builder` and `final`) are regularly published at ghcr.io and tagged with the current Ruby version: 154 | 155 | - https://github.com/ledermann/docker-rails-base/pkgs/container/rails-base-builder 156 | - https://github.com/ledermann/docker-rails-base/pkgs/container/rails-base-final 157 | 158 | Beware: The published images are **not** immutable. When a dependency (e.g. Ruby gem) is updated, the images will be republished using the **same** tag. 159 | 160 | When a new Ruby version comes out, a new tag is introduced and the images will be published using this tag and the former images will not be updated anymore. Here is a list of the tags that have been used in this repo so far: 161 | 162 | | Ruby version | Tag | First published | 163 | | ------------ | ------------ | --------------- | 164 | | 3.4.4 | 3.4.4-alpine | 2025-05-16 | 165 | | 3.4.3 | 3.4.3-alpine | 2025-04-15 | 166 | | 3.4.2 | 3.4.2-alpine | 2025-02-16 | 167 | | 3.4.1 | 3.4.1-alpine | 2024-12-28 | 168 | | 3.3.6 | 3.3.6-alpine | 2024-11-06 | 169 | | 3.3.5 | 3.3.5-alpine | 2024-09-05 | 170 | | 3.3.4 | 3.3.4-alpine | 2024-07-10 | 171 | | 3.3.3 | 3.3.3-alpine | 2024-06-13 | 172 | | 3.3.2 | 3.3.2-alpine | 2024-05-31 | 173 | | 3.3.1 | 3.3.1-alpine | 2024-04-23 | 174 | | 3.3.0 | 3.3.0-alpine | 2023-12-27 | 175 | | 3.2.2 | 3.2.2-alpine | 2023-03-31 | 176 | | 3.2.1 | 3.2.1-alpine | 2023-02-10 | 177 | | 3.2.0 | 3.2.0-alpine | 2023-01-13 | 178 | | 3.1.3 | 3.1.3-alpine | 2022-11-26 | 179 | | 3.1.2 | 3.1.2-alpine | 2022-04-13 | 180 | | 3.1.1 | 3.1.1-alpine | 2022-02-19 | 181 | | 3.1.0 | 3.1.0-alpine | 2022-01-08 | 182 | | 3.0.3 | 3.0.3-alpine | 2021-11-24 | 183 | | 3.0.2 | 3.0.2-alpine | 2021-07-08 | 184 | | 3.0.1 | 3.0.1-alpine | 2021-04-06 | 185 | | 3.0.0 | 3.0.0-alpine | 2021-02-15 | 186 | | 2.7.2 | 2.7.2-alpine | 2020-10-10 | 187 | | 2.7.1 | 2.7.1-alpine | 2020-05-20 | 188 | | 2.6.6 | - | 2020-04-01 | 189 | | 2.6.5 | - | 2020-01-24 | 190 | 191 | The latest Docker images are also tagged as `latest`. However, it is not recommended to use this tag in your Rails application, because updating an app to a new Ruby version usually requires some extra work. 192 | 193 | ## FAQ 194 | 195 | ### Why not simply use layer caching? 196 | 197 | Docker supports layer caching, so for building images it performs just the needed steps: If there is a layer from a former build and nothing has changed, it will be used. But for dependencies, this means: If a single Ruby gem in the application was updated or added, the step with `bundle install` is run again, so **all** gems will be installed again. 198 | 199 | Using a prebuilt image improves installing dependencies a lot, because only the different/updated dependencies will be installed - all existing ones will be reused. 200 | 201 | ### What if my app requires slightly different dependencies? 202 | 203 | This doesn't matter: 204 | 205 | - A missing Alpine package can be installed with `apk add` inside your app's Dockerfile. 206 | - A missing Node module (or version) will be installed with `rails assets:precompile` via the ONBUILD trigger. 207 | - A missing Ruby gem (or version) will be installed with `bundle install` via the ONBUILD trigger. 208 | 209 | ### There are gems included that my app doesn't need. Will they bloat the resulting image? 210 | 211 | No. In the build stage there is a `bundle clean --force`, which uninstalls all gems not referenced in the app's Gemfile. 212 | 213 | ### My app does not need to compile assets (e.g. API only or ImportMaps). Can I use this project? 214 | 215 | There is a workaround for this. Just add ths file to define a dummy task: 216 | 217 | ```ruby 218 | # lib/tasks/precompile.rake 219 | namespace :assets do 220 | desc 'Precompile assets' 221 | task precompile: :environment do 222 | puts 'No need to precompile assets' 223 | end 224 | end 225 | ``` 226 | 227 | In addition, you need to ensure that these files are present, even if they are not needed: 228 | 229 | ``` 230 | package.json 231 | yarn.lock 232 | .yarnrc.yml 233 | .yarn 234 | ``` 235 | 236 | You can do this by running: 237 | 238 | ```bash 239 | yarn init 240 | yarn install 241 | touch .yarnrc.yml 242 | mkdir -p .yarn 243 | touch .yarn/.keep 244 | ``` 245 | -------------------------------------------------------------------------------- /builder/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /builder/.yarnclean: -------------------------------------------------------------------------------- 1 | # test directories 2 | __tests__ 3 | test 4 | tests 5 | powered-test 6 | 7 | # asset directories 8 | docs 9 | doc 10 | website 11 | images 12 | 13 | # examples 14 | example 15 | examples 16 | 17 | # code coverage directories 18 | coverage 19 | .nyc_output 20 | 21 | # build scripts 22 | Makefile 23 | Gulpfile.js 24 | Gruntfile.js 25 | 26 | # configs 27 | appveyor.yml 28 | circle.yml 29 | codeship-services.yml 30 | codeship-steps.yml 31 | wercker.yml 32 | .tern-project 33 | .gitattributes 34 | .editorconfig 35 | .*ignore 36 | .eslintrc 37 | .jshintrc 38 | .flowconfig 39 | .documentup.json 40 | .yarn-metadata.json 41 | .travis.yml 42 | 43 | # misc 44 | *.md 45 | -------------------------------------------------------------------------------- /builder/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | # check=error=true 3 | 4 | FROM ruby:3.4.4-alpine 5 | LABEL maintainer="georg@ledermann.dev" 6 | 7 | # Add basic packages 8 | RUN apk add --no-cache \ 9 | build-base \ 10 | gcompat \ 11 | git \ 12 | imagemagick-dev \ 13 | mariadb-dev \ 14 | nodejs-current \ 15 | npm \ 16 | postgresql-dev \ 17 | sqlite-dev \ 18 | tzdata \ 19 | vips-dev \ 20 | yaml-dev 21 | 22 | WORKDIR /app 23 | 24 | ##### Install standard gems 25 | COPY Gemfile Gemfile.lock /app/ 26 | RUN bundle config --local frozen 1 && \ 27 | bundle install --jobs $(nproc) --retry 3 28 | #### 29 | 30 | #### ONBUILD: Add triggers to the image, executed later while building a child image 31 | 32 | # Arguments for the child image 33 | ONBUILD ARG SKIP_BOOTSNAP_PRECOMPILE 34 | 35 | # Copy only the files needed for installing gems 36 | ONBUILD COPY .ruby-version Gemfile Gemfile.lock /app/ 37 | ONBUILD COPY vendor/ /app/vendor/ 38 | 39 | # Install Ruby gems (for production only) 40 | ONBUILD RUN --mount=type=secret,id=bundleconfig,dst=/root/.bundle/config \ 41 | bundle config --local without 'development test' && \ 42 | bundle install --jobs $(nproc) --retry 3 && \ 43 | # Remove unneeded gems 44 | bundle clean --force && \ 45 | # Remove unneeded files from installed gems (cache, .git, *.o, *.c) 46 | rm -rf /usr/local/bundle/cache && \ 47 | rm -rf /usr/local/bundle/bundler/gems/*/.git && \ 48 | find /usr/local/bundle -type f \( \ 49 | -name '*.c' -o \ 50 | -name '*.o' -o \ 51 | -name '*.log' -o \ 52 | -name 'gem_make.out' \ 53 | \) -delete && \ 54 | find /usr/local/bundle -name '*.so' -exec strip --strip-unneeded {} + 55 | 56 | # Precompile gems with Bootsnap 57 | ONBUILD RUN if [ -z "$SKIP_BOOTSNAP_PRECOMPILE" ]; then \ 58 | bundle exec bootsnap precompile --gemfile ; \ 59 | else \ 60 | echo "Skipped precompiling gems with Bootsnap" ; \ 61 | fi 62 | 63 | # Install JavaScript dependencies 64 | ONBUILD COPY package.json yarn.lock /app/ 65 | ONBUILD COPY .yarnrc.yml .yarnrc.yml 66 | ONBUILD COPY .yarn .yarn 67 | ONBUILD RUN --mount=type=secret,id=npmrc,dst=/root/.npmrc \ 68 | --mount=type=secret,id=yarnrc,dst=/root/.yarnrc.yml \ 69 | # If your app uses Cypress for end-to-end testing, installing the binary can be slow, so we 70 | # explicitly exclude it from installing (because tests are not run here) 71 | corepack enable && \ 72 | CYPRESS_INSTALL_BINARY=0 \ 73 | yarn install --frozen-lockfile 74 | 75 | # Copy the whole application folder into the image 76 | ONBUILD COPY . /app 77 | 78 | # Precompile application code with Bootsnap 79 | ONBUILD RUN if [ -z "$SKIP_BOOTSNAP_PRECOMPILE" ]; then \ 80 | bundle exec bootsnap precompile app/ lib/ ; \ 81 | else \ 82 | echo "Skipped precompiling app with Bootsnap" ; \ 83 | fi 84 | 85 | # Precompile assets 86 | ONBUILD RUN RAILS_ENV=production \ 87 | SECRET_KEY_BASE_DUMMY=1 \ 88 | VITE_RUBY_SKIP_ASSETS_PRECOMPILE_INSTALL=true \ 89 | ./bin/rails assets:precompile && \ 90 | # remove runtime Bootsnap cache unless explicitly told not to 91 | if [ -n "$SKIP_BOOTSNAP_PRECOMPILE" ]; then \ 92 | rm -rf ./tmp/cache/bootsnap; \ 93 | fi 94 | 95 | # Remove folders not needed in resulting image 96 | # This includes `app/javascript` which contains the JavaScript source code. 97 | # Normally it is not needed in the resulting image, because it was compiled 98 | # to `public/`. But if the app uses import maps, the folder is still required 99 | # for pinning and must not be removed. 100 | ONBUILD RUN rm -rf node_modules yarn.lock .yarn .yarnrc.yml vendor/bundle test spec app/packs 101 | ONBUILD RUN if [ ! -f config/importmap.rb ]; then rm -rf app/javascript; fi 102 | -------------------------------------------------------------------------------- /builder/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby '3.4.4' 5 | 6 | # Full-stack web application framework. (https://rubyonrails.org) 7 | gem 'rails', '~> 8.0.2' 8 | 9 | # Sprockets Rails integration (https://github.com/rails/sprockets-rails) 10 | gem 'sprockets-rails' 11 | 12 | # Use Vite in Rails and bring joy to your JavaScript experience (https://github.com/ElMassimo/vite_ruby) 13 | gem 'vite_rails' 14 | 15 | # A modest JavaScript framework for the HTML you already have. (https://stimulus.hotwired.dev) 16 | gem 'stimulus-rails' 17 | 18 | # Pg is the Ruby interface to the PostgreSQL RDBMS (https://github.com/ged/ruby-pg) 19 | gem 'pg', '~> 1.1' 20 | 21 | # A Ruby/Rack web server built for parallelism. (https://puma.io) 22 | gem 'puma', '~> 6' 23 | 24 | # The speed of a single-page web application without having to write any JavaScript. (https://github.com/hotwired/turbo-rails) 25 | gem 'turbo-rails' 26 | 27 | # Sass adapter for the Rails asset pipeline. (https://github.com/rails/sass-rails) 28 | gem 'sass-rails', '>= 6' 29 | 30 | # Create JSON structures via a Builder-style DSL (https://github.com/rails/jbuilder) 31 | gem 'jbuilder' 32 | 33 | # A fast JSON parser and serializer. (http://www.ohler.com/oj) 34 | gem 'oj' 35 | 36 | # JSON Implementation for Ruby (https://ruby.github.io/json) 37 | gem 'json' 38 | 39 | # High-level wrapper for processing images for the web with ImageMagick or libvips. (https://github.com/janko/image_processing) 40 | gem 'image_processing', '~> 1.2' 41 | 42 | # Boot large ruby/rails apps faster (https://github.com/Shopify/bootsnap) 43 | gem 'bootsnap', require: false 44 | 45 | # Error reports you can be happy about. (https://www.honeybadger.io/for/ruby/) 46 | gem 'honeybadger' 47 | 48 | # Tame Rails' multi-line logging into a single line per request (https://github.com/roidrage/lograge) 49 | gem 'lograge' 50 | 51 | # The best pagination ruby gem (https://github.com/ddnexus/pagy) 52 | gem 'pagy' 53 | 54 | # Flexible authentication solution for Rails with Warden (https://github.com/heartcombo/devise) 55 | gem 'devise' 56 | 57 | # Middleware for enabling Cross-Origin Resource Sharing in Rack apps (https://github.com/cyu/rack-cors) 58 | gem 'rack-cors' 59 | 60 | # Block & throttle abusive requests (https://github.com/rack/rack-attack) 61 | gem 'rack-attack' 62 | 63 | # Rack middleware for defining a canonical host name. (https://github.com/tylerhunt/rack-canonical-host) 64 | gem 'rack-canonical-host' 65 | 66 | # Brotli compression for Rack responses (http://github.com/marcotc/rack-brotli/) 67 | gem 'rack-brotli' 68 | 69 | # Slim templates generator for Rails (https://github.com/slim-template/slim-rails) 70 | gem 'slim-rails' 71 | 72 | # A framework for building reusable, testable & encapsulated view components in Ruby on Rails. (https://viewcomponent.org) 73 | gem 'view_component' 74 | 75 | # Simple HTTP and REST client for Ruby, inspired by microframework syntax for specifying actions. (https://github.com/rest-client/rest-client) 76 | gem 'rest-client' 77 | 78 | # Simple, efficient background processing for Ruby (https://sidekiq.org) 79 | gem 'sidekiq' 80 | 81 | # Markdown that smells nice (https://github.com/vmg/redcarpet) 82 | gem 'redcarpet' 83 | 84 | # A comprehensive slugging and pretty-URL plugin. (https://github.com/norman/friendly_id) 85 | gem 'friendly_id' 86 | 87 | # SSL/TLS and general-purpose cryptography for Ruby (https://github.com/ruby/openssl) 88 | gem 'openssl' 89 | -------------------------------------------------------------------------------- /builder/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (8.0.2) 5 | actionpack (= 8.0.2) 6 | activesupport (= 8.0.2) 7 | nio4r (~> 2.0) 8 | websocket-driver (>= 0.6.1) 9 | zeitwerk (~> 2.6) 10 | actionmailbox (8.0.2) 11 | actionpack (= 8.0.2) 12 | activejob (= 8.0.2) 13 | activerecord (= 8.0.2) 14 | activestorage (= 8.0.2) 15 | activesupport (= 8.0.2) 16 | mail (>= 2.8.0) 17 | actionmailer (8.0.2) 18 | actionpack (= 8.0.2) 19 | actionview (= 8.0.2) 20 | activejob (= 8.0.2) 21 | activesupport (= 8.0.2) 22 | mail (>= 2.8.0) 23 | rails-dom-testing (~> 2.2) 24 | actionpack (8.0.2) 25 | actionview (= 8.0.2) 26 | activesupport (= 8.0.2) 27 | nokogiri (>= 1.8.5) 28 | rack (>= 2.2.4) 29 | rack-session (>= 1.0.1) 30 | rack-test (>= 0.6.3) 31 | rails-dom-testing (~> 2.2) 32 | rails-html-sanitizer (~> 1.6) 33 | useragent (~> 0.16) 34 | actiontext (8.0.2) 35 | actionpack (= 8.0.2) 36 | activerecord (= 8.0.2) 37 | activestorage (= 8.0.2) 38 | activesupport (= 8.0.2) 39 | globalid (>= 0.6.0) 40 | nokogiri (>= 1.8.5) 41 | actionview (8.0.2) 42 | activesupport (= 8.0.2) 43 | builder (~> 3.1) 44 | erubi (~> 1.11) 45 | rails-dom-testing (~> 2.2) 46 | rails-html-sanitizer (~> 1.6) 47 | activejob (8.0.2) 48 | activesupport (= 8.0.2) 49 | globalid (>= 0.3.6) 50 | activemodel (8.0.2) 51 | activesupport (= 8.0.2) 52 | activerecord (8.0.2) 53 | activemodel (= 8.0.2) 54 | activesupport (= 8.0.2) 55 | timeout (>= 0.4.0) 56 | activestorage (8.0.2) 57 | actionpack (= 8.0.2) 58 | activejob (= 8.0.2) 59 | activerecord (= 8.0.2) 60 | activesupport (= 8.0.2) 61 | marcel (~> 1.0) 62 | activesupport (8.0.2) 63 | base64 64 | benchmark (>= 0.3) 65 | bigdecimal 66 | concurrent-ruby (~> 1.0, >= 1.3.1) 67 | connection_pool (>= 2.2.5) 68 | drb 69 | i18n (>= 1.6, < 2) 70 | logger (>= 1.4.2) 71 | minitest (>= 5.1) 72 | securerandom (>= 0.3) 73 | tzinfo (~> 2.0, >= 2.0.5) 74 | uri (>= 0.13.1) 75 | addressable (2.8.7) 76 | public_suffix (>= 2.0.2, < 7.0) 77 | base64 (0.3.0) 78 | bcrypt (3.1.20) 79 | benchmark (0.4.1) 80 | bigdecimal (3.2.2) 81 | bootsnap (1.18.6) 82 | msgpack (~> 1.2) 83 | brotli (0.6.0) 84 | builder (3.3.0) 85 | concurrent-ruby (1.3.5) 86 | connection_pool (2.5.3) 87 | crass (1.0.6) 88 | date (3.4.1) 89 | devise (4.9.4) 90 | bcrypt (~> 3.0) 91 | orm_adapter (~> 0.1) 92 | railties (>= 4.1.0) 93 | responders 94 | warden (~> 1.2.3) 95 | domain_name (0.6.20240107) 96 | drb (2.2.3) 97 | dry-cli (1.2.0) 98 | erb (5.0.1) 99 | erubi (1.13.1) 100 | ffi (1.17.2-aarch64-linux-musl) 101 | ffi (1.17.2-arm64-darwin) 102 | ffi (1.17.2-x86_64-linux-gnu) 103 | ffi (1.17.2-x86_64-linux-musl) 104 | friendly_id (5.5.1) 105 | activerecord (>= 4.0.0) 106 | globalid (1.2.1) 107 | activesupport (>= 6.1) 108 | honeybadger (5.28.0) 109 | logger 110 | ostruct 111 | http-accept (1.7.0) 112 | http-cookie (1.0.8) 113 | domain_name (~> 0.5) 114 | i18n (1.14.7) 115 | concurrent-ruby (~> 1.0) 116 | image_processing (1.14.0) 117 | mini_magick (>= 4.9.5, < 6) 118 | ruby-vips (>= 2.0.17, < 3) 119 | io-console (0.8.0) 120 | irb (1.15.2) 121 | pp (>= 0.6.0) 122 | rdoc (>= 4.0.0) 123 | reline (>= 0.4.2) 124 | jbuilder (2.13.0) 125 | actionview (>= 5.0.0) 126 | activesupport (>= 5.0.0) 127 | json (2.12.2) 128 | logger (1.7.0) 129 | lograge (0.14.0) 130 | actionpack (>= 4) 131 | activesupport (>= 4) 132 | railties (>= 4) 133 | request_store (~> 1.0) 134 | loofah (2.24.1) 135 | crass (~> 1.0.2) 136 | nokogiri (>= 1.12.0) 137 | mail (2.8.1) 138 | mini_mime (>= 0.1.1) 139 | net-imap 140 | net-pop 141 | net-smtp 142 | marcel (1.0.4) 143 | method_source (1.1.0) 144 | mime-types (3.7.0) 145 | logger 146 | mime-types-data (~> 3.2025, >= 3.2025.0507) 147 | mime-types-data (3.2025.0603) 148 | mini_magick (5.2.0) 149 | benchmark 150 | logger 151 | mini_mime (1.1.5) 152 | minitest (5.25.5) 153 | msgpack (1.8.0) 154 | mutex_m (0.3.0) 155 | net-imap (0.5.8) 156 | date 157 | net-protocol 158 | net-pop (0.1.2) 159 | net-protocol 160 | net-protocol (0.2.2) 161 | timeout 162 | net-smtp (0.5.1) 163 | net-protocol 164 | netrc (0.11.0) 165 | nio4r (2.7.4) 166 | nokogiri (1.18.8-aarch64-linux-musl) 167 | racc (~> 1.4) 168 | nokogiri (1.18.8-arm64-darwin) 169 | racc (~> 1.4) 170 | nokogiri (1.18.8-x86_64-linux-gnu) 171 | racc (~> 1.4) 172 | nokogiri (1.18.8-x86_64-linux-musl) 173 | racc (~> 1.4) 174 | oj (3.16.11) 175 | bigdecimal (>= 3.0) 176 | ostruct (>= 0.2) 177 | openssl (3.3.0) 178 | orm_adapter (0.5.0) 179 | ostruct (0.6.1) 180 | pagy (9.3.4) 181 | pg (1.5.9) 182 | pp (0.6.2) 183 | prettyprint 184 | prettyprint (0.2.0) 185 | psych (5.2.6) 186 | date 187 | stringio 188 | public_suffix (6.0.2) 189 | puma (6.6.0) 190 | nio4r (~> 2.0) 191 | racc (1.8.1) 192 | rack (3.1.16) 193 | rack-attack (6.7.0) 194 | rack (>= 1.0, < 4) 195 | rack-brotli (2.0.0) 196 | brotli (>= 0.3) 197 | rack (>= 3) 198 | rack-canonical-host (1.3.0) 199 | addressable (> 0, < 3) 200 | rack (>= 1.6, < 4) 201 | rack-cors (3.0.0) 202 | logger 203 | rack (>= 3.0.14) 204 | rack-proxy (0.7.7) 205 | rack 206 | rack-session (2.1.1) 207 | base64 (>= 0.1.0) 208 | rack (>= 3.0.0) 209 | rack-test (2.2.0) 210 | rack (>= 1.3) 211 | rackup (2.2.1) 212 | rack (>= 3) 213 | rails (8.0.2) 214 | actioncable (= 8.0.2) 215 | actionmailbox (= 8.0.2) 216 | actionmailer (= 8.0.2) 217 | actionpack (= 8.0.2) 218 | actiontext (= 8.0.2) 219 | actionview (= 8.0.2) 220 | activejob (= 8.0.2) 221 | activemodel (= 8.0.2) 222 | activerecord (= 8.0.2) 223 | activestorage (= 8.0.2) 224 | activesupport (= 8.0.2) 225 | bundler (>= 1.15.0) 226 | railties (= 8.0.2) 227 | rails-dom-testing (2.3.0) 228 | activesupport (>= 5.0.0) 229 | minitest 230 | nokogiri (>= 1.6) 231 | rails-html-sanitizer (1.6.2) 232 | loofah (~> 2.21) 233 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 234 | railties (8.0.2) 235 | actionpack (= 8.0.2) 236 | activesupport (= 8.0.2) 237 | irb (~> 1.13) 238 | rackup (>= 1.0.0) 239 | rake (>= 12.2) 240 | thor (~> 1.0, >= 1.2.2) 241 | zeitwerk (~> 2.6) 242 | rake (13.3.0) 243 | rdoc (6.14.0) 244 | erb 245 | psych (>= 4.0.0) 246 | redcarpet (3.6.1) 247 | redis-client (0.24.0) 248 | connection_pool 249 | reline (0.6.1) 250 | io-console (~> 0.5) 251 | request_store (1.7.0) 252 | rack (>= 1.4) 253 | responders (3.1.1) 254 | actionpack (>= 5.2) 255 | railties (>= 5.2) 256 | rest-client (2.1.0) 257 | http-accept (>= 1.7.0, < 2.0) 258 | http-cookie (>= 1.0.2, < 2.0) 259 | mime-types (>= 1.16, < 4.0) 260 | netrc (~> 0.8) 261 | ruby-vips (2.2.4) 262 | ffi (~> 1.12) 263 | logger 264 | sass-rails (6.0.0) 265 | sassc-rails (~> 2.1, >= 2.1.1) 266 | sassc (2.4.0) 267 | ffi (~> 1.9) 268 | sassc-rails (2.1.2) 269 | railties (>= 4.0.0) 270 | sassc (>= 2.0) 271 | sprockets (> 3.0) 272 | sprockets-rails 273 | tilt 274 | securerandom (0.4.1) 275 | sidekiq (8.0.4) 276 | connection_pool (>= 2.5.0) 277 | json (>= 2.9.0) 278 | logger (>= 1.6.2) 279 | rack (>= 3.1.0) 280 | redis-client (>= 0.23.2) 281 | slim (5.2.1) 282 | temple (~> 0.10.0) 283 | tilt (>= 2.1.0) 284 | slim-rails (3.7.0) 285 | actionpack (>= 3.1) 286 | railties (>= 3.1) 287 | slim (>= 3.0, < 6.0, != 5.0.0) 288 | sprockets (4.2.2) 289 | concurrent-ruby (~> 1.0) 290 | logger 291 | rack (>= 2.2.4, < 4) 292 | sprockets-rails (3.5.2) 293 | actionpack (>= 6.1) 294 | activesupport (>= 6.1) 295 | sprockets (>= 3.0.0) 296 | stimulus-rails (1.3.4) 297 | railties (>= 6.0.0) 298 | stringio (3.1.7) 299 | temple (0.10.3) 300 | thor (1.3.2) 301 | tilt (2.6.0) 302 | timeout (0.4.3) 303 | turbo-rails (2.0.16) 304 | actionpack (>= 7.1.0) 305 | railties (>= 7.1.0) 306 | tzinfo (2.0.6) 307 | concurrent-ruby (~> 1.0) 308 | uri (1.0.3) 309 | useragent (0.16.11) 310 | view_component (3.23.2) 311 | activesupport (>= 5.2.0, < 8.1) 312 | concurrent-ruby (~> 1) 313 | method_source (~> 1.0) 314 | vite_rails (3.0.19) 315 | railties (>= 5.1, < 9) 316 | vite_ruby (~> 3.0, >= 3.2.2) 317 | vite_ruby (3.9.2) 318 | dry-cli (>= 0.7, < 2) 319 | logger (~> 1.6) 320 | mutex_m 321 | rack-proxy (~> 0.6, >= 0.6.1) 322 | zeitwerk (~> 2.2) 323 | warden (1.2.9) 324 | rack (>= 2.0.9) 325 | websocket-driver (0.8.0) 326 | base64 327 | websocket-extensions (>= 0.1.0) 328 | websocket-extensions (0.1.5) 329 | zeitwerk (2.7.3) 330 | 331 | PLATFORMS 332 | aarch64-linux-musl 333 | arm64-darwin-24 334 | x86_64-linux 335 | x86_64-linux-musl 336 | 337 | DEPENDENCIES 338 | bootsnap 339 | devise 340 | friendly_id 341 | honeybadger 342 | image_processing (~> 1.2) 343 | jbuilder 344 | json 345 | lograge 346 | oj 347 | openssl 348 | pagy 349 | pg (~> 1.1) 350 | puma (~> 6) 351 | rack-attack 352 | rack-brotli 353 | rack-canonical-host 354 | rack-cors 355 | rails (~> 8.0.2) 356 | redcarpet 357 | rest-client 358 | sass-rails (>= 6) 359 | sidekiq 360 | slim-rails 361 | sprockets-rails 362 | stimulus-rails 363 | turbo-rails 364 | view_component 365 | vite_rails 366 | 367 | RUBY VERSION 368 | ruby 3.4.4p34 369 | 370 | BUNDLED WITH 371 | 2.6.9 372 | -------------------------------------------------------------------------------- /builder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": {} 4 | } 5 | -------------------------------------------------------------------------------- /builder/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /final/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | # check=error=true 3 | 4 | FROM ruby:3.4.4-alpine 5 | LABEL maintainer="georg@ledermann.dev" 6 | 7 | # Add basic packages 8 | RUN apk add --no-cache \ 9 | brotli-libs \ 10 | gcompat \ 11 | jemalloc \ 12 | postgresql-client \ 13 | tzdata 14 | 15 | # Configure Rails 16 | ENV RAILS_LOG_TO_STDOUT=true \ 17 | RAILS_SERVE_STATIC_FILES=true \ 18 | RAILS_ENV=production 19 | 20 | WORKDIR /app 21 | 22 | # Expose Puma port 23 | EXPOSE 3000 24 | 25 | # Enable jemalloc for reduced memory usage and latency 26 | ENV LD_PRELOAD=/usr/lib/libjemalloc.so.2 27 | 28 | # Write GIT meta data from arguments to env vars 29 | ONBUILD ARG COMMIT_SHA 30 | ONBUILD ARG COMMIT_TIME 31 | ONBUILD ARG COMMIT_VERSION 32 | ONBUILD ARG COMMIT_BRANCH 33 | 34 | ONBUILD ENV COMMIT_SHA=${COMMIT_SHA} 35 | ONBUILD ENV COMMIT_TIME=${COMMIT_TIME} 36 | ONBUILD ENV COMMIT_VERSION=${COMMIT_VERSION} 37 | ONBUILD ENV COMMIT_BRANCH=${COMMIT_BRANCH} 38 | 39 | # Add user 40 | ONBUILD RUN addgroup -g 1000 -S app && \ 41 | adduser -u 1000 -S app -G app 42 | 43 | # Copy app with gems from former build stage 44 | ONBUILD COPY --from=builder --chown=app:app /usr/local/bundle/ /usr/local/bundle/ 45 | ONBUILD COPY --from=builder --chown=app:app /app /app 46 | --------------------------------------------------------------------------------