-
Notifications
You must be signed in to change notification settings - Fork 152
Expand file tree
/
Copy pathDockerfile
More file actions
207 lines (163 loc) · 8.59 KB
/
Copy pathDockerfile
File metadata and controls
207 lines (163 loc) · 8.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# syntax=docker/dockerfile:1.19
# check=error=true;skip=SecretsUsedInArgOrEnv
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t harbor .
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name harbor harbor
# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=4.0.6
# Bun 1.4 canary includes the package alias fix needed for the TypeScript 7 migration.
FROM docker.io/oven/bun:canary-slim@sha256:6e28b54849cf680251afd7ed83e24375dfe47b6f78c8bfdb7e98bf12a6fa9f2e AS bun
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS ruby-base
# Rails app lives here
WORKDIR /rails
# Keeping Bun on the common parent makes the same pinned binary available to
# dependency installation, asset compilation, and the production SSR runtime.
COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun
# Set production environment
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development:test"
# Build the runtime base independently from application dependencies so these
# packages are installed in parallel with the builder toolchain and Bundler.
FROM ruby-base AS runtime-base
# Install base packages
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y \
curl \
fontconfig \
libjemalloc2 \
tar && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Install only the compiler toolchain needed by native gems.
FROM ruby-base AS build-base
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y g++ gcc make && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Install Git and the font conversion tool independently so native gem
# compilation does not wait for unrelated build packages.
FROM ruby-base AS frontend-base
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y git woff2 && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Install JavaScript dependencies independently so BuildKit can run this stage
# in parallel with the Ruby dependency stage.
FROM frontend-base AS javascript-dependencies
COPY package.json bun.lock bunfig.toml ./
RUN --mount=type=cache,target=/root/.bun/install/cache \
bun i --frozen-lockfile --linker=isolated && \
mkdir -p node_modules/.vite-client node_modules/.vite-ssr node_modules/.vite-temp
RUN cp node_modules/@fontsource-variable/spline-sans/files/spline-sans-latin-wght-normal.woff2 /tmp/spline-sans-latin-wght-normal.woff2 && \
woff2_decompress /tmp/spline-sans-latin-wght-normal.woff2 && \
install -Dm644 /tmp/spline-sans-latin-wght-normal.ttf vendor/fonts/spline-sans-latin-wght-normal.ttf
# Sharp ships one libvips binary with its codecs. Ruby Vips loads the same ABI.
FROM javascript-dependencies AS libvips
RUN mkdir /libvips && \
cp node_modules/@img/sharp-libvips-linux-*/lib/libvips-cpp.so.* /libvips/libvips-cpp.so
# Prepare the runtime concurrently with dependency and asset compilation.
FROM runtime-base AS prepared-runtime
ENV LD_LIBRARY_PATH="/usr/local/lib"
COPY --from=frontend-base /usr/bin/git /usr/bin/git
COPY --from=frontend-base /usr/lib/git-core /usr/lib/git-core
COPY --from=libvips /libvips/libvips-cpp.so /usr/local/lib/libvips-cpp.so
COPY --from=javascript-dependencies /rails/vendor/fonts/spline-sans-latin-wght-normal.ttf \
/usr/local/share/fonts/spline-sans/spline-sans-latin-wght-normal.ttf
RUN ln -s libvips-cpp.so /usr/local/lib/libvips.so.42 && \
fc-cache -f /usr/local/share/fonts/spline-sans && \
groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
git config --system http.timeout 30 && \
git config --system http.lowSpeedLimit 1000 && \
git config --system http.lowSpeedTime 10
# Install gems independently from JavaScript dependencies.
FROM build-base AS ruby-dependencies
COPY Gemfile Gemfile.lock ./
RUN --mount=type=cache,target=/root/.bundle/cache \
--mount=type=cache,target=/usr/local/bundle/ruby/4.0.0/cache \
BUNDLER_VERSION="$(awk 'END { print $1 }' Gemfile.lock)" && \
(gem list --installed bundler --version "$BUNDLER_VERSION" || \
gem install bundler --version "$BUNDLER_VERSION" --no-document) && \
bundle "_${BUNDLER_VERSION}_" install && \
rm -rf "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git
# Copy application source without persisting build-only dependencies in its
# layers. Rails and Blume consume those dependencies through read-only mounts.
FROM ruby-base AS application-source
COPY --from=javascript-dependencies /rails/vendor/fonts /rails/vendor/fonts
COPY --exclude=blume.config.ts --exclude=docs . .
# Build Blume from only its inputs so Rails application changes neither
# invalidate nor delay documentation generation.
FROM ruby-base AS docs-assets
COPY package.json blume.config.ts theme.css ./
COPY config/themes.yml config/themes.yml
COPY public public
COPY docs docs
RUN --mount=type=bind,from=javascript-dependencies,source=/rails/node_modules,target=/rails/node_modules,rw \
bun run build:docs
# Generate route helpers before the two asset branches start.
FROM application-source AS route-helpers
RUN --network=none \
--mount=type=bind,from=ruby-dependencies,source=/usr/local/bundle,target=/usr/local/bundle \
export SECRET_KEY_BASE_DUMMY=1 JS_FROM_ROUTES_FORCE=true && \
AWS_EC2_METADATA_DISABLED=true \
S3_BUCKET=dummy S3_ACCESS_KEY_ID=dummy S3_SECRET_ACCESS_KEY=dummy S3_ENDPOINT=http://127.0.0.1 \
./bin/rake js_from_routes:generate
# Build Rails assets in one Rails process. Save its Bootsnap cache in the image
# so production does not compile the same Ruby files again at boot.
FROM route-helpers AS rails-assets
RUN --network=none \
--mount=type=bind,from=ruby-dependencies,source=/usr/local/bundle,target=/usr/local/bundle \
--mount=type=cache,target=/root/.cache/bootsnap \
export SECRET_KEY_BASE_DUMMY=1 BOOTSNAP_CACHE_DIR=/root/.cache/bootsnap \
VITE_RUBY_SKIP_ASSETS_PRECOMPILE_EXTENSION=true && \
AWS_EC2_METADATA_DISABLED=true \
S3_BUCKET=dummy S3_ACCESS_KEY_ID=dummy S3_SECRET_ACCESS_KEY=dummy S3_ENDPOINT=http://127.0.0.1 \
./bin/rails runner 'Rails.application.eager_load!; Rails.application.load_tasks; Rake::Task["assets:precompile"].invoke' && \
rm -rf tmp/cache/bootsnap && \
cp -a "$BOOTSNAP_CACHE_DIR/bootsnap" tmp/cache/bootsnap
# Build Vite from only the files that can affect its output. Tailwind scans
# Rails controllers, helpers, and views in addition to the JavaScript source.
FROM ruby-base AS frontend-assets
COPY app/javascript app/javascript
COPY app/assets/tailwind app/assets/tailwind
COPY app/controllers app/controllers
COPY app/helpers app/helpers
COPY app/views app/views
COPY config/vite.json config/vite.json
COPY package.json ./
COPY svelte.config.js ./
COPY tsconfig.json tsconfig.node.json ./
COPY vite.config.ts ./
COPY --from=route-helpers /rails/app/javascript/api app/javascript/api
RUN --mount=type=bind,from=javascript-dependencies,source=/rails/node_modules,target=/rails/node_modules \
--mount=type=cache,target=/rails/node_modules/.vite-client \
--mount=type=cache,target=/rails/node_modules/.vite-ssr \
--mount=type=tmpfs,target=/rails/node_modules/.vite-temp \
--mount=type=cache,target=/root/.bun/install/cache \
(VITE_CACHE_DIR=node_modules/.vite-client bun x --bun vite build & \
client_pid=$!; \
VITE_CACHE_DIR=node_modules/.vite-ssr bun x --bun vite build --ssr & \
ssr_pid=$!; \
wait "$client_pid" && \
wait "$ssr_pid")
# Combine generated Rails, Vite, and documentation assets.
FROM rails-assets AS build
COPY --from=frontend-assets /rails/public /rails/public
COPY --from=docs-assets /rails/public /rails/public
# Final stage for app image
FROM prepared-runtime
# Copy built artifacts: gems, application
COPY --from=ruby-dependencies "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build --exclude=db --exclude=log --exclude=spec --exclude=storage --exclude=test --exclude=tmp /rails /rails
COPY --from=build --chown=1000:1000 /rails/db /rails/db
COPY --from=build --chown=1000:1000 /rails/log /rails/log
COPY --from=build --chown=1000:1000 /rails/storage /rails/storage
COPY --from=build --chown=1000:1000 /rails/tmp /rails/tmp
ARG SOURCE_COMMIT=unknown
RUN printf '%s\n' "$SOURCE_COMMIT" > REVISION
USER 1000:1000
# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start either web server or job worker based on WORKER env var
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]