Dockerfile
Rama main · réplica de lectura del repositorio original.
# 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
FROM docker.io/oven/bun:1.4-slim 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 \
tzdata && \
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 libffi-dev 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 ./
COPY patches patches
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
# image_processing 2.0 loads Ruby Vips during Rails boot, so the stages that
# boot Rails below (route helpers, asset precompilation) need libvips too.
ENV LD_LIBRARY_PATH="/usr/local/lib"
COPY --from=libvips /libvips/libvips-cpp.so /usr/local/lib/libvips-cpp.so
RUN ln -s libvips-cpp.so /usr/local/lib/libvips.so.42
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"]