diff --git a/Dockerfile b/Dockerfile index 0a9f2cd..f607fae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,98 +1,86 @@ # syntax=docker/dockerfile:1 # check=error=true -# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: -# docker build -t example . -# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name example example - -# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html - +# --- Version Arguments --- +ARG RUBY_VERSION=3.2.4 +ARG NODE_VERSION=22.16.0 +ARG YARN_VERSION=1.22.22 ARG COMMIT_SHA ARG COMMIT_MESSAGE -# Make sure RUBY_VERSION matches the Ruby version in .ruby-version -ARG RUBY_VERSION=3.2.4 -FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base +# --- Stage 1: Grab Node.js Binaries --- +# We use the official image as a source for pre-compiled binaries +FROM docker.io/library/node:${NODE_VERSION}-slim AS node_source +# --- Stage 2: Base (Runtime Environment) --- +FROM docker.io/library/ruby:${RUBY_VERSION}-slim AS base -# Rails app lives here WORKDIR /rails -# Install base packages +# Install runtime-only system packages RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y curl libjemalloc2 libvips libpq-dev nodejs telnet imagemagick && \ + apt-get install --no-install-recommends -y \ + curl libjemalloc2 libvips libpq-dev telnet imagemagick && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives +# "Heist" Node.js and npm from the node_source stage +COPY --from=node_source /usr/local/bin/node /usr/local/bin/node +COPY --from=node_source /usr/local/lib/node_modules /usr/local/lib/node_modules + +# Re-link npm and install Yarn (Instantly available) +RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \ + npm install -g yarn@${YARN_VERSION} + # Set production environment ENV RAILS_ENV="production" \ BUNDLE_DEPLOYMENT="1" \ BUNDLE_PATH="/usr/local/bundle" \ BUNDLE_WITHOUT="development" -# Throw-away build stage to reduce size of final image +# --- Stage 3: Build (Compilation & Assets) --- FROM base AS build # Install packages needed to build gems and node modules RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y build-essential git libyaml-dev node-gyp pkg-config python-is-python3 && \ + apt-get install --no-install-recommends -y \ + build-essential git libyaml-dev node-gyp pkg-config python-is-python3 && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives -# Install JavaScript dependencies -ARG NODE_VERSION=22.16.0 -ARG YARN_VERSION=1.22.22 -ENV PATH=/usr/local/node/bin:$PATH -RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ - /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ - npm install -g yarn@$YARN_VERSION && \ - rm -rf /tmp/node-build-master - -# Install application gems +# 1. Install Gems (Separate COPY for maximum caching) COPY Gemfile Gemfile.lock ./ RUN bundle install && \ rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ bundle exec bootsnap precompile --gemfile -# Install node modules +# 2. Install JavaScript dependencies (Separate COPY for maximum caching) COPY package.json yarn.lock ./ RUN yarn install --immutable -# Copy application code +# 3. Copy application code COPY . . -# Precompile bootsnap code for faster boot times -RUN bundle exec bootsnap precompile app/ lib/ +# 4. Precompile assets and bootsnap +RUN bundle exec bootsnap precompile app/ lib/ && \ + SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile && \ + rm -rf node_modules tmp/cache -# Precompiling assets for production without requiring secret RAILS_MASTER_KEY -RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile - - -RUN rm -rf node_modules - - -# Final stage for app image +# --- Stage 4: Final Production Image --- FROM base ARG COMMIT_SHA ARG COMMIT_MESSAGE -# Copy built artifacts: gems, application +# Copy built artifacts: gems and the app (with precompiled assets) COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" COPY --from=build /rails /rails -# Add build information so we can check if the deployment works -RUN echo "Commit SHA: ${COMMIT_SHA}" > /rails/public/versioninfo && \ - echo "Commit message: ${COMMIT_MESSAGE}" >> /rails/public/versioninfo -RUN echo "versioninfo: SHA is [${COMMIT_SHA}] and DATE is [${BUILD_DATE}]" - -# Run and own only the runtime files as a non-root user for security +# Security: Run as non-root user RUN groupadd --system --gid 1000 rails && \ useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ chown -R rails:rails db log tmp public USER 1000:1000 -# Entrypoint prepares the database. ENTRYPOINT ["/rails/bin/docker-entrypoint"] -# Start server via Thruster by default, this can be overwritten at runtime EXPOSE 80 CMD ["./bin/thrust", "./bin/rails", "server"] diff --git a/public/versioninfo b/public/versioninfo new file mode 100644 index 0000000..b629f1e --- /dev/null +++ b/public/versioninfo @@ -0,0 +1 @@ +So 8. Feb 22:59:33 CET 2026