Skip to content

Commit b320299

Browse files
committed
feature: ability to use Docker hardened image
1 parent 591c56e commit b320299

File tree

6 files changed

+100
-2
lines changed

6 files changed

+100
-2
lines changed

DEMO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ docker buildx build . -t rails-demo
190190
docker run -p 3000:3000 -e RAILS_MASTER_KEY=$(cat config/master.key) --rm rails-demo
191191
```
192192

193-
# Demo 5 - Bunding Javascript (esbuild)
193+
# Demo 5 - Bundling Javascript (esbuild)
194194

195195
While optional, bundling Javascript is a popular choice, and starting with
196196
Rails 7 there are three options: esbuild, rollup, and webpack. The

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ different contents. If both are specified, `--force` takes precedence.
3636

3737
* `--alpine` - use [alpine](https://www.alpinelinux.org/) as base image (requires [Alpine <= 3.18 OR Rails >= 8.0](https://github.com/sparklemotion/sqlite3-ruby/issues/434))
3838
* `--fullstaq` - use [fullstaq](https://fullstaqruby.org/) [images](https://github.com/evilmartians/fullstaq-ruby-docker) on [quay.io](https://quay.io/repository/evl.ms/fullstaq-ruby?tab=tags&tag=latest)
39+
* `--hardened` - use `dhi.io/ruby:$RUBY_VERSION-dev` as base image (mutually exclusive with `--alpine` or `--fullstaq`)
3940
* `--jemalloc` - use [jemalloc](https://jemalloc.net/) memory allocator
4041
* `--swap=n` - allocate swap space. See [falloc options](https://man7.org/linux/man-pages/man1/fallocate.1.html#OPTIONS) for suffixes
4142
* `--yjit` - enable [YJIT](https://github.com/ruby/ruby/blob/master/doc/yjit/yjit.md) optimizing compiler
@@ -50,7 +51,7 @@ different contents. If both are specified, `--force` takes precedence.
5051

5152
* `--ci` - include test gems in deployed image
5253
* `--compose` - generate a `docker-compose.yml` file
53-
* `--max-idle=n` - exit afer *n* seconds of inactivity. Supports [iso 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) and [sleep](https://man7.org/linux/man-pages/man1/sleep.1.html#DESCRIPTION) syntaxes. Uses passenger for now, awaiting [puma](https://github.com/puma/puma/issues/2580) support.
54+
* `--max-idle=n` - exit after *n* seconds of inactivity. Supports [iso 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) and [sleep](https://man7.org/linux/man-pages/man1/sleep.1.html#DESCRIPTION) syntaxes. Uses passenger for now, awaiting [puma](https://github.com/puma/puma/issues/2580) support.
5455
* `--nginx` - serve static files via [nginx](https://www.nginx.com/). May require `--root` on some targets to access `/dev/stdout`
5556
* `--thruster` - serve static files via [thruster](https://github.com/basecamp/thruster?tab=readme-ov-file#thruster).
5657
* `--link` - add [--link](https://docs.docker.com/engine/reference/builder/#copy---link) to COPY statements. Some tools (like at the moment, [buildah](https://www.redhat.com/en/topics/containers/what-is-buildah)) don't yet support this feature.

lib/generators/dockerfile_generator.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class DockerfileGenerator < Rails::Generators::Base
1616
"compose" => false,
1717
"fullstaq" => false,
1818
"gemfile-updates" => true,
19+
"hardened" => false,
1920
"jemalloc" => true,
2021
"label" => {},
2122
"link" => false,
@@ -200,6 +201,9 @@ class DockerfileGenerator < Rails::Generators::Base
200201
class_option :fullstaq, type: :boolean, default: OPTION_DEFAULTS.fullstaq,
201202
descr: "use Fullstaq Ruby image from Quay.io"
202203

204+
class_option :hardened, type: :boolean, default: OPTION_DEFAULTS.hardened,
205+
desc: "use hardened Ruby image from dhi.io"
206+
203207
class_option :yjit, type: :boolean, default: OPTION_DEFAULTS.yjit,
204208
desc: "enable YJIT optimizing compiler"
205209

@@ -294,6 +298,10 @@ class DockerfileGenerator < Rails::Generators::Base
294298
def generate_app
295299
source_paths.push File.expand_path("./templates", __dir__)
296300

301+
if options.hardened? && (options.fullstaq? || options.alpine?)
302+
raise Thor::Error, "--hardened is mutually exclusive with --fullstaq and --alpine"
303+
end
304+
297305
# merge options
298306
options.label.replace(@@labels.merge(options.label).select { |key, value| value != "" })
299307

lib/generators/templates/Dockerfile.erb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ ARG RUBY_VERSION=<%= RUBY_VERSION %>
1818
<% end -%>
1919
<% if options.fullstaq -%>
2020
FROM <%= platform %>quay.io/evl.ms/fullstaq-ruby:${RUBY_VERSION}-<%= options.jemalloc ? 'jemalloc-' : 'malloctrim-' %><%= variant %><% unless options.precompile == "defer" %> AS base<% end %>
21+
<% elsif options.hardened -%>
22+
FROM <%= platform %>dhi.io/ruby:$RUBY_VERSION-dev<% unless options.precompile == "defer" %> AS base<% end %>
2123
<% else -%>
2224
FROM <%= platform %><%= options.registry %>ruby:$RUBY_VERSION-<%= variant %><% unless options.precompile == "defer" %> AS base<% end %>
2325
<% end -%>

test/results/hardened/Dockerfile

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# syntax=docker/dockerfile:1
2+
# check=error=true
3+
4+
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
5+
ARG RUBY_VERSION=xxx
6+
FROM dhi.io/ruby:$RUBY_VERSION-dev AS base
7+
8+
# Rails app lives here
9+
WORKDIR /rails
10+
11+
# Update gems and bundler
12+
RUN gem update --system --no-document && \
13+
gem install -N bundler
14+
15+
# Install base packages
16+
RUN apt-get update -qq && \
17+
apt-get install --no-install-recommends -y curl libjemalloc2 sqlite3 && \
18+
rm -rf /var/lib/apt/lists /var/cache/apt/archives
19+
20+
# Set production environment
21+
ENV BUNDLE_DEPLOYMENT="1" \
22+
BUNDLE_PATH="/usr/local/bundle" \
23+
BUNDLE_WITHOUT="development:test" \
24+
RAILS_ENV="production"
25+
26+
27+
# Throw-away build stage to reduce size of final image
28+
FROM base AS build
29+
30+
# Install packages needed to build gems
31+
RUN apt-get update -qq && \
32+
apt-get install --no-install-recommends -y build-essential libyaml-dev pkg-config && \
33+
rm -rf /var/lib/apt/lists /var/cache/apt/archives
34+
35+
# Install application gems
36+
COPY Gemfile Gemfile.lock ./
37+
RUN bundle install && \
38+
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git
39+
40+
# Copy application code
41+
COPY . .
42+
43+
# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
44+
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
45+
46+
47+
# Final stage for app image
48+
FROM base
49+
50+
51+
# Copy built artifacts: gems, application
52+
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
53+
COPY --from=build /rails /rails
54+
55+
# Run and own only the runtime files as a non-root user for security
56+
RUN groupadd --system --gid 1000 rails && \
57+
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
58+
mkdir /data && \
59+
chown -R 1000:1000 db log storage tmp /data
60+
USER 1000:1000
61+
62+
# Deployment options
63+
ENV DATABASE_URL="sqlite3:///data/production.sqlite3"
64+
65+
# Entrypoint prepares the database.
66+
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
67+
68+
# Start the server by default, this can be overwritten at runtime
69+
EXPOSE 3000
70+
VOLUME /data
71+
CMD ["./bin/rails", "server"]

test/test_hardened.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "base"
4+
require "yaml"
5+
6+
class TestHardened < TestBase
7+
@rails_options = "--minimal"
8+
@generate_options = "--hardened"
9+
10+
def test_hardened
11+
check_dockerfile
12+
13+
options = YAML.load_file("config/dockerfile.yml")["options"]
14+
assert_equal true, options["hardened"]
15+
end
16+
end

0 commit comments

Comments
 (0)