Move Dockerfile deploy installs to base stage

I’m utilizing the new Dockerfile template with Rails, nice work @Sam_Ruby! I noticed that there are some apt-get steps running after the copy line(COPY --link . .) which means Docker can’t cache those steps and is always going to run the deploy apt-get steps.

Since the final stage is from the base stage what about just running the apt-get within the base stage? I’ve tested it and it works fine with a new app. With a cached build it’s just the final stage that actually gets built, everything else is from cache.

# syntax = docker/dockerfile:1

ARG RUBY_VERSION=3.2.2
FROM ruby:$RUBY_VERSION-slim as base

WORKDIR /rails

ENV RAILS_ENV="production" \
    BUNDLE_PATH="/usr/local/bundle" \
    BUNDLE_WITHOUT="development:test" \
    BUNDLE_DEPLOYMENT="1"

# *** THIS WAS MOVED FROM THE FINAL STAGE TO THE BASE STAGE ***
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y default-mysql-client curl libvips && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

FROM base as build

RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential default-libmysqlclient-dev git libvips

COPY --link Gemfile Gemfile.lock ./
RUN bundle install -j 8 && \
    bundle exec bootsnap precompile --gemfile && \
    rm -rf ~/.bundle/ $BUNDLE_PATH/ruby/*/cache $BUNDLE_PATH/ruby/*/bundler/gems/*/.git

COPY --link . .

RUN bundle exec bootsnap precompile app/ lib/

RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile

# Final stage for app image
FROM base

RUN useradd rails --home /rails --shell /bin/bash
USER rails:rails

COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build --chown=rails:rails /rails /rails

ENTRYPOINT ["/rails/bin/docker-entrypoint"]

EXPOSE 3000
CMD ["./bin/rails", "server"]

First, I’m confused. The docker file template included in Rails 7.1 doesn’t include --link; not because it isn’t recommended, but it isn’t supported by the chosen CI infrastructure (buildah).

Moving something from the final stage to the build stage means that those packages won’t be deployed. As an example, you won’t have libvips to process images.

From a caching perspective, it is important to look at each stage separately. Yes, the apt-get is after the COPY statement, but there is an intervening FROM statement. What’s important, therefore, is that the apt-get is before the COPY --from=build statements. Stages will be build in parallel so each of the apt-gets will run concurrently, and then the final build stage will stall at the COPY --from=build statement until the bulld is complete.

If you are using dockerfile-rails and really want to explore caching, try turning on the --cache option.

1 Like

Ah, looks like --link was removed, this was from a few months ago so that makes sense.

I’m using this with mrsk actually which is using buildx and taking care of loading from cache, I just generated the initial Dockerfile with dockerfile-rails. The build stage cache is always going to break with even just a controller change because of the COPY . . command.

I’m just looking to speed up builds and not run an apt-get update with every build which is happening with any application change. I meant to say I moved that apt-get line to the base stage, not the build stage since the final stage is from the base stage, I updated the original post to reflect that.

I see the additional --cache option with the dockerfile-rails generator which adds some mounts, I’ll check that out as well.

The build stage cache is always going to break with even just a controller change because of the COPY . . command

There are no apt-get lines in the build stage after the COPY . . command.

I’m seeing this work fine and not break the cache locally now, I was thinking the COPY --from=build ended up breaking the build cache for the final stage. Appreciate the explanation!