Skip to content

Commit 67d951d

Browse files
committed
Add production-ready Docker image
The docker image is not meant for local development, but for a production-level deployment. It is based on the Docker setup added by default for new Rails apps. As part of this change, we had to rethink our integration of JavaScript and CSS compressors. Without the change in `production.rb`, an ExecJS runtime would be needed to run the Rails app. Since we require a JavaScript runtime only for the asset compilation (but not when serving the application), this would bloat the Docker image unnecessarily.
1 parent 5553ce8 commit 67d951d

9 files changed

Lines changed: 203 additions & 8 deletions

File tree

.dockerignore

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
2+
3+
# Ignore git directory.
4+
/.git/
5+
/.gitignore
6+
7+
# Ignore bundler config.
8+
/.bundle
9+
10+
# Ignore all environment files (except templates).
11+
/.env*
12+
!/.env*.erb
13+
14+
# Ignore all default key files.
15+
/config/master.key
16+
/config/credentials/*.key
17+
18+
# Ignore all logfiles and tempfiles.
19+
/log/*
20+
/tmp/*
21+
!/log/.keep
22+
!/tmp/.keep
23+
24+
# Ignore pidfiles, but keep the directory.
25+
/tmp/pids/*
26+
!/tmp/pids/.keep
27+
28+
# Ignore storage (uploaded files in development and any SQLite databases).
29+
/storage/*
30+
!/storage/.keep
31+
/tmp/storage/*
32+
!/tmp/storage/.keep
33+
/public/uploads
34+
35+
# Ignore assets.
36+
/node_modules/
37+
/app/assets/builds/*
38+
!/app/assets/builds/.keep
39+
/public/packs
40+
/public/packs-test
41+
/public/assets
42+
43+
# Ignore CI service files.
44+
/.github
45+
46+
# Ignore development files
47+
/.devcontainer
48+
49+
# Ignore Docker-related files
50+
/.dockerignore
51+
/Dockerfile*
52+
53+
# Ignore temporary files
54+
/coverage
55+
/rubocop.html
56+
*.DS_Store
57+
58+
## Ignore editor-specific artifacts
59+
# Vagrant
60+
/.vagrant
61+
/vagrant
62+
# Sublime
63+
*.sublime-*
64+
# IntelliJ
65+
/.idea
66+
*.iml
67+
68+
# Ignore Byebug command history file.
69+
.byebug_history
70+
71+
# Ignore yarn files
72+
/yarn-error.log
73+
yarn-debug.log*
74+
.yarn-integrity
75+
# Ignore more Yarn files per documentation:
76+
# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
77+
.pnp.*
78+
.yarn/*
79+
!.yarn/patches
80+
!.yarn/plugins
81+
!.yarn/releases
82+
!.yarn/sdks
83+
!.yarn/versions

.github/dependabot.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,13 @@ updates:
3030
labels:
3131
- dependencies
3232
- github-actions
33+
34+
- package-ecosystem: docker
35+
directory: "/"
36+
schedule:
37+
interval: daily
38+
time: "03:00"
39+
timezone: UTC
40+
labels:
41+
- dependencies
42+
- docker

Dockerfile

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# syntax = docker/dockerfile:1
2+
3+
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
4+
# docker build -t my-app .
5+
# docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY=<value from config/master.key> my-app
6+
7+
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
8+
ARG RUBY_VERSION=3.3.5
9+
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
10+
11+
# Rails app lives here
12+
WORKDIR /rails
13+
14+
# Install base packages
15+
RUN apt-get update -qq && \
16+
apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client && \
17+
rm -rf /var/lib/apt/lists /var/cache/apt/archives
18+
19+
# Set production environment
20+
ENV RAILS_ENV="production" \
21+
BUNDLE_DEPLOYMENT="1" \
22+
BUNDLE_PATH="/usr/local/bundle" \
23+
BUNDLE_WITHOUT="development:test"
24+
25+
# Throw-away build stage to reduce size of final image
26+
FROM base AS build
27+
28+
# Install packages needed to build gems and node modules
29+
RUN apt-get update -qq && \
30+
apt-get install --no-install-recommends -y build-essential git libpq-dev node-gyp pkg-config python-is-python3 && \
31+
rm -rf /var/lib/apt/lists /var/cache/apt/archives
32+
33+
# Install JavaScript dependencies
34+
ARG NODE_VERSION=22.9.0
35+
ENV PATH=/usr/local/node/bin:$PATH
36+
RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
37+
/tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
38+
corepack enable && \
39+
rm -rf /tmp/node-build-master
40+
41+
# Install application gems
42+
COPY Gemfile Gemfile.lock ./
43+
RUN bundle install && \
44+
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
45+
bundle exec bootsnap precompile --gemfile
46+
47+
# Install node modules
48+
COPY package.json yarn.lock .yarnrc.yml ./
49+
RUN yarn install --immutable
50+
51+
# Copy application code
52+
COPY . .
53+
54+
# Precompile bootsnap code for faster boot times
55+
RUN bundle exec bootsnap precompile app/ lib/
56+
57+
# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
58+
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
59+
60+
61+
RUN rm -rf node_modules
62+
63+
64+
# Final stage for app image
65+
FROM base
66+
67+
# Copy built artifacts: gems, application
68+
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
69+
COPY --from=build /rails /rails
70+
71+
# Run and own only the runtime files as a non-root user for security
72+
RUN groupadd --system --gid 1000 rails && \
73+
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
74+
chown -R rails:rails db log storage tmp
75+
USER 1000:1000
76+
77+
# Entrypoint prepares the database.
78+
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
79+
80+
# Start the server by default, this can be overwritten at runtime
81+
EXPOSE 7500
82+
CMD ["./bin/rails", "server"]

Gemfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ gem 'acts-as-taggable-on'
66
gem 'bcrypt'
77
gem 'bootsnap', require: false
88
gem 'bootstrap-will_paginate'
9-
gem 'coffee-rails', '>= 5.0.0' # Use CoffeeScript for .coffee assets and views
9+
gem 'coffee-rails', require: false
1010
gem 'config'
1111
gem 'devise-bootstrap-views'
1212
gem 'faraday'
@@ -43,7 +43,7 @@ gem 'simple_form'
4343
gem 'slim-rails'
4444
gem 'solid_queue'
4545
gem 'sprockets-rails'
46-
gem 'terser'
46+
gem 'terser', require: false
4747
gem 'turbolinks'
4848
gem 'whenever', require: false
4949

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ DEPENDENCIES
609609
bootstrap-will_paginate
610610
brakeman
611611
capybara
612-
coffee-rails (>= 5.0.0)
612+
coffee-rails
613613
config
614614
database_cleaner
615615
devise (~> 4.9)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
CodeHarbor is a repository system for automatically gradeable programming exercises and enables instructors to exchange of such exercises via the [ProFormA XML](https://github.com/ProFormA/proformaxml) format across diverse code assessment systems.
99

1010
## Server Setup and Deployment
11-
Use [Capistrano](https://capistranorb.com/). Docker and Vagrant are for local development only.
11+
Use [Capistrano](https://capistranorb.com/). or the provided Dockerfile (only for production). Vagrant is for local development only.
1212

1313

1414
## Development Setup

bin/docker-entrypoint

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash -e
2+
3+
# Enable jemalloc for reduced memory usage and latency.
4+
if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then
5+
export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)"
6+
fi
7+
8+
# If running the rails server then create or migrate existing database
9+
if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
10+
./bin/rails db:prepare
11+
fi
12+
13+
exec "${@}"

config/environments/production.rb

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,17 @@
2525
# Disable serving static files from `public/`, relying on NGINX/Apache to do so instead.
2626
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
2727

28-
# Compress CSS using a preprocessor.
29-
config.assets.css_compressor = :sass
28+
config.assets.configure do |env|
29+
require 'coffee-rails'
30+
require 'terser'
31+
env.register_compressor 'application/javascript', :terser, Terser::Compressor
3032

31-
# Compress JavaScript using a preprocessor.
32-
config.assets.js_compressor = :terser
33+
# Compress CSS using a preprocessor.
34+
config.assets.css_compressor = :sass
35+
36+
# Compress JavaScript using a preprocessor.
37+
config.assets.js_compressor = :terser
38+
end
3339

3440
# Do not fall back to assets pipeline if a precompiled asset is missed.
3541
config.assets.compile = false

docs/environment_variables.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The following environment variables are specifically support in CodeHarbor and a
77
| `RAILS_ENV` | `development` | Specifies the Rails environment which can be configured using the files in `config/environments` |
88
| `SECRET_KEY_BASE` | ` ` | Specifies a server-side secret for Rails, must be set for production |
99
| `RAILS_RELATIVE_URL_ROOT` | `/` | Specifies the subpath of the application, used for links and assets |
10+
| `DATABASE_URL` | ` ` | Specifies database parameters to use, rather than those specified in `config/database.yml`. A valid URL would be `postgresql://username:password@host:5432/database?pool=5` |
1011
| `WEB_CONCURRENCY` | Number of physical CPU cores | Puma worker count in production for cluster mode |
1112
| `RAILS_MAX_THREADS` | `3` | Maximum Puma thread count per worker |
1213
| `PORT` | `7500` | Default port for the web server |

0 commit comments

Comments
 (0)