Struggling after generating Rails Dockerfile

I’ve got an app that was set up quite a while ago, before the dockerfile-rails. I am making changes to the app and decided to get the Dockerfile in line with the latest stuff, so I installed the gem and generated a Dockerfile. Here it is:

# syntax = docker/dockerfile:1

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.2.2
FROM ruby:$RUBY_VERSION-slim as base

# Rails app lives here
WORKDIR /rails

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

# Update gems and bundler
RUN gem update --system --no-document && \
    gem install -N bundler


# Throw-away build stage to reduce size of final image
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 curl libpq-dev node-gyp pkg-config python-is-python3

# Install JavaScript dependencies
ARG NODE_VERSION=16.13.0
ARG YARN_VERSION=1.22.21
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
COPY --link Gemfile Gemfile.lock ./
RUN bundle install && \
    bundle exec bootsnap precompile --gemfile && \
    rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git

# Install node modules
COPY --link .yarnrc package.json yarn.lock ./
COPY --link .yarn/releases/* .yarn/releases/
RUN yarn install --frozen-lockfile

# Copy application code
COPY --link . .

# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/

# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE=DUMMY ./bin/rails assets:precompile


# Final stage for app image
FROM base

# Install packages needed for deployment
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl postgresql-client && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails

# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
    useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
    chown -R 1000:1000 db log tmp
USER 1000:1000

# Deployment options
ENV RAILS_LOG_TO_STDOUT="1" \
    RAILS_SERVE_STATIC_FILES="true"

# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]

When I try to fly deploy (on my local machine or via the GitHub Actions workflow I set up) I get the same error. Here’s the logs from GitHub Actions:

#14 [build  2/10] RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ &&     /tmp/node-build-master/bin/node-build "14" /usr/local/node &&     npm install -g yarn@1.22.21 &&     rm -rf /tmp/node-build-master
#14 2.934 node-build: definition not found: 14
#14 ERROR: executor failed running [/bin/sh -c 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]: exit code: 2
------
 > [build  2/10] RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ &&     /tmp/node-build-master/bin/node-build "14" /usr/local/node &&     npm install -g yarn@1.22.21 &&     rm -rf /tmp/node-build-master:
#14 2.934 node-build: definition not found: 14
------
Error: failed to fetch an image or build from source: error building: failed to solve: executor failed running [/bin/sh -c 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]: exit code: 2

This looks like the $NODE_VERSION variable has been overridden and the value is “14” rather than “16.13.0”.

What could be going on here?

I tried add ENV NODE_VERSION=16.13.0 to override any variable set from somewhere else. This “worked”, in that this part of the build process succeeded. Unfortunately, the build still failed on something that I would expect to work out of the box with this Dockerfile, though:

#16 [build  4/10] RUN bundle install &&     bundle exec bootsnap precompile --gemfile &&     rm -rf ~/.bundle/ "/usr/local/bundle"/ruby/*/cache "/usr/local/bundle"/ruby/*/bundler/gems/*/.git
#16 0.593 Your Ruby version is 3.0.2, but your Gemfile specified 3.2.2
#16 ERROR: executor failed running [/bin/sh -c bundle install &&     bundle exec bootsnap precompile --gemfile &&     rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git]: exit code: 18
------
 > [build  4/10] RUN bundle install &&     bundle exec bootsnap precompile --gemfile &&     rm -rf ~/.bundle/ "/usr/local/bundle"/ruby/*/cache "/usr/local/bundle"/ruby/*/bundler/gems/*/.git:
#16 0.593 Your Ruby version is 3.0.2, but your Gemfile specified 3.2.2
------
Error: failed to fetch an image or build from source: error building: failed to solve: executor failed running [/bin/sh -c bundle install &&     bundle exec bootsnap precompile --gemfile &&     rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git]: exit code: 18

I’ve now also tried deleting the builder and running fly deploy --no-cache in case this was some kind of caching issue. Both issues persist.

Do you have a script that runs fly deploy? The only way this should happen is that you are passing --build-arg NODE_VERSION=14 on the call to fly deploy.

One thing to try is to change ARG NODE_VERSION=16.13.0 (which enables overriding) to ENV NODE_VERSION=16.13.0 (which does not).

The GitHub Action that deploys it looks like this:

  deploy:
    name: Deploy
    needs: [runTests]
    runs-on: ubuntu-latest
    env:
      FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
    steps:
      - uses: actions/checkout@v2
      - uses: superfly/flyctl-actions/setup-flyctl@master
      - run: flyctl deploy --remote-only --build-arg RAILS_ENV=production --build-arg RAILS_MASTER_KEY=${{ secrets.RAILS_PRODUCTION_KEY }}

No weird build args, just the Rails master key which was required by an older versions of the Dockerfile that I was using previously.

In an effort to debug this, I generated a new Rails 7.1 app (this app was upgraded to Rails 7.1) and grabbed the Dockerfile from it to try that. It’s not significantly different from the one I had generated with the fly-apps/dockerfile-rails (thanks to you, if I’m not mistaken :pray:). This hasn’t resulted in any change in this error.

Switching to ENV NODE_VERSION=16.13.0, resolves the Node error, but this now crashes because the Ruby version is wrong?


#14 [build 3/9] COPY Gemfile Gemfile.lock ./
#14 DONE 0.0s

#15 [build 4/9] RUN bundle install &&     rm -rf ~/.bundle/ "/usr/local/bundle"/ruby/*/cache "/usr/local/bundle"/ruby/*/bundler/gems/*/.git &&     bundle exec bootsnap precompile --gemfile
#15 0.422 Warning: the running version of Bundler (2.2.22) is older than the version that created the lockfile (2.5.5). We suggest you to upgrade to the version that created the lockfile by running `gem install bundler:2.5.5`.
#15 0.431 Your Ruby version is 3.0.2, but your Gemfile specified 3.2.2
#15 ERROR: executor failed running [/bin/sh -c bundle install &&     rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git &&     bundle exec bootsnap precompile --gemfile]: exit code: 18
------
 > [build 4/9] RUN bundle install &&     rm -rf ~/.bundle/ "/usr/local/bundle"/ruby/*/cache "/usr/local/bundle"/ruby/*/bundler/gems/*/.git &&     bundle exec bootsnap precompile --gemfile:
#15 0.422 Warning: the running version of Bundler (2.2.22) is older than the version that created the lockfile (2.5.5). We suggest you to upgrade to the version that created the lockfile by running `gem install bundler:2.5.5`.
#15 0.431 Your Ruby version is 3.0.2, but your Gemfile specified 3.2.2
------
Error: failed to fetch an image or build from source: error building: failed to solve: executor failed running [/bin/sh -c bundle install &&     rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git &&     bundle exec bootsnap precompile --gemfile]: exit code: 18

Here’s the version of the Dockerfile that produced the error above:

# syntax = docker/dockerfile:1

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.2.2
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base

# Rails app lives here
WORKDIR /rails

# 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
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 curl git libpq-dev libvips node-gyp pkg-config python-is-python3

# Install JavaScript dependencies
ENV NODE_VERSION=16.13.0
ARG YARN_VERSION=1.22.21
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
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
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

# Copy application code
COPY . .

# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/

# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile


# Final stage for app image
FROM base

# Install packages needed for deployment
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libvips postgresql-client && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Copy built artifacts: gems, application
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /rails /rails

# Run and own only the runtime files as a non-root user for security
RUN useradd rails --create-home --shell /bin/bash && \
    chown -R rails:rails db log storage tmp
USER rails:rails

# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]

You can check the Rails sources, I wrote most of the Rails dockerfile generator. I also am the primary author of the fly dockerfile generator.

I recommend using bin/rails generate dockerfile over the Dockerfile that 7.1 provides, but either way you will find that RUBY_VERSION in the Dockerfile matches the version you have installed locally.

If you run bundle update locally then Gemfile.lock will be updated to match that same Ruby version. After you have done that, that will resolve this problem. Whether that is the final problem or not, I can’t say, but if you ask here I should be able to get you further.

Thanks for your work on all this, and your help here.

I switched back to the Dockerfile generated by the gem (with only the switch to use ENV rather than ARG to set the NODE_VERSION) and ran bundle update. The Ruby version error persists when I try to deploy.

Here is the error:

#14 [build  2/10] RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ &&     /tmp/node-build-master/bin/node-build "16.13.0" /usr/local/node &&     npm install -g yarn@1.22.21 &&     rm -rf /tmp/node-build-master
#14 CACHED

#15 [build  3/10] COPY --link Gemfile Gemfile.lock ./
#15 DONE 0.0s

#16 [build  4/10] RUN bundle install &&     bundle exec bootsnap precompile --gemfile &&     rm -rf ~/.bundle/ "/usr/local/bundle"/ruby/*/cache "/usr/local/bundle"/ruby/*/bundler/gems/*/.git
#16 0.511 Your Ruby version is 3.0.2, but your Gemfile specified 3.2.2
#16 ERROR: executor failed running [/bin/sh -c bundle install &&     bundle exec bootsnap precompile --gemfile &&     rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git]: exit code: 18

#1 [internal] load build definition from Dockerfile
------
 > [build  4/10] RUN bundle install &&     bundle exec bootsnap precompile --gemfile &&     rm -rf ~/.bundle/ "/usr/local/bundle"/ruby/*/cache "/usr/local/bundle"/ruby/*/bundler/gems/*/.git:
#16 0.511 Your Ruby version is 3.0.2, but your Gemfile specified 3.2.2
------
Error: failed to fetch an image or build from source: error building: failed to solve: executor failed running [/bin/sh -c bundle install &&     bundle exec bootsnap precompile --gemfile &&     rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git]: exit code: 18

Here is the Dockerfile:

# syntax = docker/dockerfile:1

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.2.2
FROM ruby:$RUBY_VERSION-slim as base

# Rails app lives here
WORKDIR /rails

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

# Update gems and bundler
RUN gem update --system --no-document && \
    gem install -N bundler


# Throw-away build stage to reduce size of final image
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 curl libpq-dev node-gyp pkg-config python-is-python3

# Install JavaScript dependencies
ENV NODE_VERSION=16.13.0
ARG YARN_VERSION=1.22.21
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
COPY --link Gemfile Gemfile.lock ./
RUN bundle install && \
    bundle exec bootsnap precompile --gemfile && \
    rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git

# Install node modules
COPY --link .yarnrc package.json yarn.lock ./
COPY --link .yarn/releases/* .yarn/releases/
RUN yarn install --frozen-lockfile

# Copy application code
COPY --link . .

# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/

# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE=DUMMY ./bin/rails assets:precompile


# Final stage for app image
FROM base

# Install packages needed for deployment
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libjemalloc2 postgresql-client && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails

# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
    useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
    chown -R 1000:1000 db log tmp
USER 1000:1000

# Deployment options
ENV LD_PRELOAD="libjemalloc.so.2" \
    MALLOC_CONF="dirty_decay_ms:1000,narenas:2,background_thread:true" \
    RAILS_LOG_TO_STDOUT="1" \
    RAILS_SERVE_STATIC_FILES="true" \
    RUBY_YJIT_ENABLE="1"

# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]

I get the same result when I run fly deploy --no-cache locally. I am very confused.

Check your Gemfile?

Gemfile correctly has ruby "3.2.2" in it, which matches what’s in the Dockerfile, what’s in my .ruby-version, what’s in the Gemfile.lock, what’s running on my system, and what I’m intending to run. :sweat_smile:

What’s in your fly.toml?

Oh no… I’m so sorry for wasting your time on this. It contains build args that specify the wrong Ruby and Node versions.

Not a problem! Glad to help!

1 Like