Giter VIP home page Giter VIP logo

dockerfile-rails's Introduction

Overview

Provides a Rails generator to produce Dockerfiles and related files. This is being proposed as the generator to be included in Rails 7.1, and a substantial number of pull requests along those lines have already been merged. This repository contains fixes and features beyond those pull requests. Highlights:

  • Supports all Rails supported releases, not just Rails 7.1, and likely works with a number of previous releases.
  • Can be customized using flags on the generate dockerfile command, and rerun to produce a custom tailored dockerfile based on detecting the actual features used by your application.
  • Will set .node_version, packageManager and install gems if needed to deploy your application.
  • Can produce a docker-compose.yml file for locally testing your configuration before deploying.

For more background:

  • Motivation - why this generator was created and what problems it is meant to solve
  • Demos - scripts to copy and paste into an empty directory to launch demo apps
  • Test Results - expected outputs for each test

Usage

Install from the root of your Rails project by running the following.

bundle add dockerfile-rails --optimistic --group development
bin/rails generate dockerfile

The --optimistic flag will make sure you always get the latest dockerfile-rails gem when you run bundle update && rails g dockerfile.

General option:

  • --force - overwrite existing files
  • --skip - keep existing files

If neither are specified, you will be prompted if a file exists with different contents. If both are specified, --force takes precedence.

Runtime Optimizations:

Build optimizations:

  • --cache - use build caching to speed up builds
  • --parallel - use multi-stage builds to install gems and node modules in parallel

Add/remove a Feature:

  • --ci - include test gems in deployed image
  • --compose - generate a docker-compose.yml file
  • --max-idle=n - exit afer n seconds of inactivity. Supports iso 8601 and sleep syntaxes. Uses passenger for now, awaiting puma support.
  • --nginx - serve static files via nginx. May require --root on some targets to access /dev/stdout
  • --thruster - serve static files via thruster.
  • --no-link - don't add --link to COPY statements. Some tools (like at the moment, buildah) don't yet support this feature.
  • --no-lock - don't add linux platforms, set BUNDLE_DEPLOY, or --frozen-lockfile. May be needed at times to work around a rubygems bug.
  • --sudo - install and configure sudo to enable sudo -iu rails access to full environment

Error Tracking & Alerting:

  • --rollbar - install gem and a default initializer for Rollbar
  • --sentry - install gems and a default initializer for Sentry

Add a Database:

Generally the dockerfile generator will be able to determine what dependencies you are actually using. But should you be using DATABASE_URL, for example, at runtime additional support may be needed:

  • --litefs - use LiteFS
  • --mysql - add mysql libraries
  • --postgresql - add postgresql libraries
  • --redis - add redis libraries
  • --sqlite3 - add sqlite3 libraries
  • --sqlserver - add SQL Server libraries

Add a package/environment variable/build argument:

Not all of your needs can be determined by scanning your application. For example, I like to add vim and procps.

  • --add package... - add one or more debian packages
  • --arg=name:value - add a build argument
  • --env=name:value - add an environment variable
  • --remove package... - remove package from "to be added" list

Args and environment variables can be tailored to a specific build phase by adding -base, -build, or -deploy after the flag name (e.g --add-build freetds-dev --add-deploy freetds-bin). If no such suffix is found, the default for arg is -base, and the default for env is -deploy. Removal of an arg or environment variable is done by leaving the value blank (e.g --env-build=PORT:).

Configuration:

  • --bin-cd - adjust binstubs to set current working directory autocrlf enabled or may not be able to set bin stubs as executable.
  • --label=name:value - specify docker label. Can be used multiple times. See LABEL for detail
  • --no-prepare - omit db:prepare. Useful for cloud platforms with release phases
  • --passenger - use Phusion Passenger under nginx
  • --platform=s - specify target platform. See FROM for details
  • --variant=s - dockerhub ruby variant, defaults to slim. See docker official images for list.
  • --precompile=defer - may be needed when your configuration requires access to secrets that are not available at build time. Results in larger images and slower deployments.
  • --root - run application as root
  • --windows - make Dockerfile work for Windows users that may have set git config --global core.autocrlf true
  • --private-gemserver-domain=gems.example.com - set the domain name of your private gemserver. This is used to tell bundler for what domain to use the credentials of a private gemserver provided via a docker secret
  • --no-precompiled-gems - compile all gems instead of using precompiled versions

Advanced Customization:

There may be times where feature detection plus flags just aren't enough. As an example, you may wish to configure and run multiple processes.

  • --instructions=path - a dockerfile fragment to be inserted into the final document.
  • --migrate=cmd - a replacement (generally a script) for db:prepare/db:migrate.
  • --no-gemfile-updates - do not modify my gemfile.
  • --procfile=path - a Procfile to use in place of launching Rails directly.
  • --registry=another.docker.registry.com - use a different registry for sourcing Docker images (e.g. public.ecr.aws).

Like with environment variables, packages, and build args, --instructions can be tailored to a specific build phase by adding -base, -build, or -deploy after the flag name, with the default being -deploy.

Additionally, if the instructions start with a shebang instead the file being treated as a Dockerfile fragment, the file is treated as a script and a RUN statement is added to your Dockerfile instead.


Options are saved between runs into config/dockerfile.yml. To invert a boolean options, add or remove a no- prefix from the option name.

Testing

A single invocation of rake test:all will run all of the tests defined. dockerfile-rails has are three types of tests:

  • rake test:rubocop runs rubocop using the same options as the Rails codebase.
  • rake test:system creates a new esbuild application, generates a dockerfile, builds and runs it. As this is time consuming, only one application is tested this way at this time, and a --javascript example was selected as it exercises a large portion of the features.
  • rake test runs integration tests, as described below

The current integration testing strategy is to run rails new and generate dockerfile with various configurations and compare the generated artifacts with expected results. ARG values in Dockerfiles are masked before comparison.

Running all integration tests, or even a single individual test can be done as follows:

rake test

bundle exec rake test TEST=test/test_minimal.rb
bundle exec ruby test/test_minimal.rb

To assist with this process, outputs of tests can be captured automatically. This is useful when adding new tests and when making a change that affects many tests. Be sure to inspect the output (e.g., by using git diff) before committing.

rake test:capture

If you are running a single test, the following environment variables settings may be helpful:

  • RAILS_ENV=TEST will match the environment used to produce the captured outputs.
  • TEST_CAPTURE=1 will capture test results.
  • TEST_KEEP=1 will leave the test app behind for inspection after the test completes.

Historical Links

The following links relate to the coordination between this package and Rails 7.1.

Parallel efforts for Hanami:

dockerfile-rails's People

Contributors

albus522 avatar antsiscool avatar bf4 avatar bradgessler avatar byellokore avatar cypher avatar gabebw avatar ibrahima avatar januszm avatar jwcooper avatar kevinebaugh avatar kinduff avatar louim avatar luizkowalski avatar mthadley avatar neanias avatar pil0u avatar primdahl avatar qelphybox avatar rmehner avatar rubys avatar sato11 avatar scart88 avatar steveclarke avatar tmaier avatar tomczak-mateusz avatar ttilberg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dockerfile-rails's Issues

Error installing psych (5.1.2) when creating DockerFile with Rails 7.1.2

๐Ÿ‘‹ Thanks for your contribution.

Issue:
I have a new Rails 7.1.2 which after the creation of the app I deleted the default DockerFile that comes with Rails 7.1.x
and run the command.

bin/rails generate dockerfile --fullstaq --jemalloc

Then when I tried to check that the image builds with

docker build .

I got this error.

23.69 An error occurred while installing psych (5.1.2), and Bundler cannot continue.
23.69 
23.69 In Gemfile:
23.69   importmap-rails was resolved to 1.2.3, which depends on
23.69     railties was resolved to 7.1.2, which depends on
23.69       irb was resolved to 1.10.1, which depends on
23.69         rdoc was resolved to 6.6.2, which depends on
23.69           psych

....
....
....

Dockerfile:32
--------------------
 31 |     COPY --link Gemfile Gemfile.lock ./
 32 | >>> RUN bundle install && \
 33 | >>>     bundle exec bootsnap precompile --gemfile && \
 34 | >>>     rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git
 35 |     
--------------------
ERROR: failed to solve: process "/bin/bash -o pipefail -c bundle install &&     bundle exec bootsnap precompile --gemfile &&     rm -rf ~/.bundle/ \"${BUNDLE_PATH}\"/ruby/*/cache \"${BUNDLE_PATH}\"/ruby/*/bundler/gems/*/.git" did not complete successfully: exit code: 5

Doing some extra research it turns out that Phych 5.x.x requires libyaml on MacOS and libyaml-dev on ubuntu.
ruby/psych#602

Resolution:

Adding libyaml-dev in the DockerFile builds the image successfully.

Thanks

Node being installed even if Bun is being used

hey ๐Ÿ‘‹๐Ÿป

I see this issue where node is installed in the image and .node-version is generated even if Bun is being used. This can cause problems, as I found before.

I think that using_node? method just checks for package.json existence which is not 100% correct since Bun also uses this. I believe that using_node? should also check I'm not using_bun?

Change location of IRB history file

If you connect to a docker container and run rails console, IRB will attempt to record a history file in /rails/.irb_history, but this location is not writeable.

Perhaps configuring IRB to record this to /rails/log/irb_history.log would be preferable. Users can already do this manually by including a .irbrc file in the root of their project, so it may be helpful to create a default one if that file does not exist.

Getting an error from "lib/generators/templates/_install_node.erb"

(erb):23:in `get_binding': undefined method `split' for nil:NilClass (NoMethodError)
	from /Users/shin/.rbenv/versions/3.1.3/lib/ruby/3.1.0/erb.rb:905:in `eval'
	from /Users/shin/.rbenv/versions/3.1.3/lib/ruby/3.1.0/erb.rb:905:in `result'
	from /Users/shin/.rbenv/versions/3.1.3/lib/ruby/gems/3.1.0/gems/dockerfile-rails-1.0.7/lib/generators/dockerfile_generator.rb:142:in `render'
	from /Users/shin/.rbenv/versions/3.1.3/lib/ruby/gems/3.1.0/gems/dockerfile-rails-1.0.7/lib/generators/templates/Dockerfile.erb:57:in `template'

I was trying to deploy my app to Fly.io and saw this error. How should I solve this error?

Copy local gems specified with path in Gemfile

We have a couple of generated gems that are in the same repo as our application.

Bundle install fails if they haven't been copied to docker image.

Would it be possible to look through the Gemfile/ Gemfile.lock for all gems specified with with a path and copy them before running bundle install

Install Bun when it is being used instead of Node

The latest jsbundling-rails release includes the ability to use bun as well. It would be nice if when the Dockerfile is being generated, it could also detect bun as the js runtime (probably via the presence of bun.lockb or bun.config.js files and use that instead of Node

Unable to load Jemalloc in compose

Hey! I bumped the gem to the latest version and I started seeing:

ERROR: ld.so: object '/usr/lib/x86_64-linux-gnu/libjemalloc.so.2' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.

in my docker compose logs. A wild guess is that it's related to the newly added security lockdown features, but I thought i'd ask before digging in.

Order of `COPY` commands doesn't allow for Gemfile's `file` directive

I'm trying to use ruby file: ".ruby-version" on my Gemfile so I just have one file to update Ruby's version but I noticed I can't deploy on Fly with this change, I get the error that .ruby-version does not exist.

I noticed that COPY --link Gemfile Gemfile.lock ./ happens before COPY . . so at the bundle install phase, .ruby-version file is not there yet. There are a couple of ways to fix this, either put COPY . . before running bundle (not great) or simply copy .ruby-version before (or with) copying gemfile/gemfile.lock, but there's a catch: not everyone will have this file (or maybe the file is in the gitignore/dockerignore list)

thinking if this could be fixed somehow. any ideas?

Missing RUN statement when using execjs and Yarn

When generating a Dockerfile for a project that uses execjs and Yarn, the "Install yarn" section of the generated Dockerfile is missing a RUN instruction.

Steps to reproduce

The steps below were used to recreate the issue and the result is available here: https://github.com/jeff-french/dockerfile-rails-issue-reproduction

# Check environment
ruby --version
# ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]
rails --version
# Rails 7.1.3
node --version
# v20.11.0
yarn --version
# 4.0.2

# New rails project
rails new blog
cd blog

# Setup Yarn
corepack enable
yarn init -2
yarn set version stable
yarn install

# Add execjs
bundle add execjs

# dockerfile-rails
bundle add dockerfile-rails --optimistic --group development
rails g dockerfile --force

Actual output

This will produce an invalid section starting around line 40 of the generated Dockerfile

# Install yarn
ARG YARN_VERSION=4.0.2
    corepack enable && \
    corepack prepare yarn@$YARN_VERSION --activate

Expected output

# Install yarn
ARG YARN_VERSION=4.0.2
RUN corepack enable && \
    corepack prepare yarn@$YARN_VERSION --activate

Investigation

The issue appears to be caused when the install_node template is called the second time to setup Yarn:

<% elsif using_node? and (!using_execjs? || File.exist?('yarn.lock')) -%>
<%= render partial: 'install_node', locals: {node_version: using_execjs? ? nil : node_version, yarn_version: File.exist?('yarn.lock') ? yarn_version : nil} %>
<% end -%>

In this call node_version gets set to nil due to the usage of execjs and yarn_version get set to 4.0.2.

When the install_node template gets rendered from this call, the following section gets skipped entirely since node_version=nil:

<% if node_version && node_version != 'lts' -%>
<% if options.alpine? -%>
ENV PATH=/usr/local/node/bin:$PATH
RUN curl -sL https://unofficial-builds.nodejs.org/download/release/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64-musl.tar.gz | tar xz -C /tmp/ && \
mkdir /usr/local/node && \
cp -rp /tmp/node-v${NODE_VERSION}-linux-x64-musl/* /usr/local/node/ && \
<% else -%>
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 && \
<% end -%>
<% end -%>

and then this section attempts to append to the RUN command from the previously skipped section:

<% if yarn_version -%>
<% if yarn_version < '2' -%>
<% if node_version -%> <% else %>RUN<% end %> npm install -g yarn@$YARN_VERSION<% if node_version -%> && \<% end %>
<% else -%>
<% if node_version && (node_version.split('.').map(&:to_i) <=> [16,10,0]) < 0 -%>
npm i -g corepack && \
<% else -%>
corepack enable && \
<% end -%>
corepack prepare yarn@$YARN_VERSION --activate<% if node_version -%> && \<% end %>
<% end -%>
<% end -%>

Solution

I was able to get the expected output by changing the following section:

<% else -%>
corepack enable && \
<% end -%>

to

<% else -%>
<% if node_version -%>   <% else %>RUN<% end %> corepack enable && \
<% end -%>

Caveats

This fix worked in this particular situation but I'm not sure if it is a drop in solution when execjs is not being used and the node_install ends up rendering the full template.

vendor directory is empty in CI

Hello, I am trying to use this dockerfile in a rails 6.1 project to run tests in Gitlab CI.

The build step runs fine with kaniko, but the test step fails with bundler: command not found: rails. When I check what the vendor directory contains, it looks OK in the build step, but empty in the test step.

This is my dockerfile.yml

# generated by dockerfile-rails

---
options:
  cache: true
  ci: true
  jemalloc: true
  mysql: true
  redis: true
  root: true
  packages:
    build:
    - git
    - ruby-dev
    - gcc
    - make
    - g++
    - libffi-dev
    - libmariadb-dev-compat

This is the Dockerfile

# syntax = docker/dockerfile:1

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

# Rails app lives here
WORKDIR /rails

# Set production environment
ENV RAILS_ENV="production" \
    BUNDLE_WITHOUT="development" \
    BUNDLE_DEPLOYMENT="1"

# 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 --mount=type=cache,id=dev-apt-cache,sharing=locked,target=/var/cache/apt \
    --mount=type=cache,id=dev-apt-lib,sharing=locked,target=/var/lib/apt \
    apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential curl default-libmysqlclient-dev g++ gcc git libffi-dev libmariadb-dev-compat make node-gyp pkg-config python-is-python3 ruby-dev

# Install Node.js
ARG NODE_VERSION=17.6.0
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 && \
    rm -rf /tmp/node-build-master

# Install application gems
COPY --link Gemfile Gemfile.lock ./
RUN --mount=type=cache,id=bld-gem-cache,sharing=locked,target=/srv/vendor \
    bundle config set app_config .bundle && \
    bundle config set path /srv/vendor && \
    bundle install && \
    bundle exec bootsnap precompile --gemfile && \
    bundle clean && \
    mkdir -p vendor && \
    bundle config set path vendor && \
    cp -ar /srv/vendor .

# Install node modules
COPY --link package.json package-lock.json ./
RUN --mount=type=cache,id=bld-npm-cache,target=/root/.npm \
    npm install

# Copy application code
COPY --link . .

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


# Final stage for app image
FROM base

# Install packages needed for deployment
RUN --mount=type=cache,id=dev-apt-cache,sharing=locked,target=/var/cache/apt \
    --mount=type=cache,id=dev-apt-lib,sharing=locked,target=/var/lib/apt \
    apt-get update -qq && \
    apt-get install --no-install-recommends -y curl default-mysql-client imagemagick libjemalloc2

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

# 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"

# 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"]

This is the kaniko command in the build step:

  script:
    - mkdir -p /kaniko/.docker
    - export container=docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor --force --cache=false --context $CI_PROJECT_DIR --dockerfile Dockerfile --build-arg BUILDKIT_INLINE_CACHE=1 --build-arg RAILS_MASTER_KEY=$MASTER_KEY --destination $CI_REGISTRY_IMAGE:$DESTINATION_TAG

[Suggestion] Prominently warn or error if on arm with no platform specified

We just encountered a deploy issue because we were generating the docker file on an M1 Mac and deploying to x86 without explicitly setting the platform.

Given the intention of this effort is to operate on the principle of least surprise, that most deploy environments are still x86, and M(1|2) Macs are going to be more common, it would be helpful to provide a notice that you may be generating unintentional results without specifying the --platform option.

Erroring and asking to declare the --platform option might be the cleanest option, but a bit heavy handed. A very obvious warning might be better. The warning/error could be isolated to if Gem::Platform.local.cpu is used as a fallback.

I love this project... is it considered ready for primetime?

If so, are you able to add a a Changelog file or Release Notes for the tags, in order to show the purpose behind the updates. I've tried to follow the commits but it's hard to get a high level view of the purpose for the updates.

Much appreciated for the time spent on this. Impressive project. Thanks!

Don't default to sqlite3, or provide option to disable?

I'm using SQL Server in my app. When I build a Dockerfile without specifying a database, it assumes I wish to use Sqlite3.

My Dockerfile is generated with the following additional lines:

RUN mkdir /data
# ...
ENV DATABASE_URL="sqlite3:///data/production.sqlite3" 
# ...
VOLUME /data

A few notes:

  • The scanner.rb correctly does not set @sqlite3 = true
  • However dockerfile_generator.rb sets a default to sqlite3
  • The Readme seems to imply that if this tool cannot infer the database correctly, you will need to specify it, such as specifying --sqlite3 from the Add a Database section.

I can appreciate that adding sqlite3 as a default is probably convenient or likely, however, if it is being used, it will be in the gemfile, or the database config, and as such, will be detected and should not be assumed unless specified. Because it is, I am manually editing the Dockerfile, even though it's otherwise entirely autogenerated from my config/dockerfile.yml config file.

Would you consider removing the default value for #deploy_database, and require checking as you do for pg and mysql?

Fly: `--swap` without `--root` lead to permission problems with fly volumes

I followed Sqlite3 on fly volumes tutorial and proactively enabled swap for demo rails app.

It turned out that, since the container is launched as root user when swap is on, fly mounts volumes for root too:
Mounting /dev/vdb at /mnt/volume w/ uid: 0, gid: 0 and chmod 0755

then entry-point script substitutes user for rails server command with rails user:

https://github.com/rubys/dockerfile-rails/blob/69f7631407735aa558bf99be46e0c0c988435479/lib/generators/templates/docker-entrypoint.erb#L18

rendering mounted volumes un-accessible for rails user.

`--max-idle` option generates corrupted Dockerfile

How to reproduce:

  1. Run bin/rails generate dockerfile --cache --parallel --yjit --max-idle=3600
  2. Under the configure nginx and passenger section of the generated Dockerfile, note how EOF appears twice in a row instead of above, rending the Dockerfile corrupted.
COPY <<-'EOF' /etc/nginx/sites-enabled/default
server {
    listen 3000;
    root /rails/public;
    passenger_enabled on;
    passenger_ctl hook_detached_process /etc/nginx/hook_detached_process;
    passenger_min_instances 0;
    passenger_pool_idle_time ;
}
                                                              # <<<<<<<<<<<<< SHOULD BE HERE
COPY <<-'EOF' /etc/nginx/sites-enabled/hook_detached_process
#!/usr/bin/env ruby
status = `passenger-status`
processes = status[/^Processes\s*:\s*(\d*)/, 1].to_i
system 'nginx -s stop' if processes == 0
EOF                                                           # <<<<<<<<<<<<< THIS
EOF

Gems installed with git break the scanner

Hey Sam!

Thanks for all the work you've put into this gem. I'm currently running in an error from the scanner.rb when trying to determine if git is needed. My gemfile has multiple gems requiring git. This is the stacktrace when running bin/rails generate dockerfile

/Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/bundler-2.3.25/lib/bundler/source/git/git_proxy.rb:224:in `allowed_with_path': The git source https://github.com/shrinerb/shrine.git is not yet checked out. Please run `bundle install` before trying to start your application (Bundler::GitError)
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/bundler-2.3.25/lib/bundler/source/git/git_proxy.rb:190:in `find_local_revision'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/bundler-2.3.25/lib/bundler/source/git/git_proxy.rb:62:in `revision'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/bundler-2.3.25/lib/bundler/source/git.rb:234:in `revision'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/bundler-2.3.25/lib/bundler/source/git.rb:103:in `install_path'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/bundler-2.3.25/lib/bundler/definition.rb:284:in `block in spec_git_paths'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/bundler-2.3.25/lib/bundler/definition.rb:284:in `map'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/bundler-2.3.25/lib/bundler/definition.rb:284:in `spec_git_paths'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/dockerfile-rails-0.4.3/lib/dockerfile-rails/scanner.rb:31:in `scan_rails_app'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/dockerfile-rails-0.4.3/lib/generators/dockerfile_generator.rb:48:in `generate_app'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/invocation.rb:134:in `block in invoke_all'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/invocation.rb:134:in `each'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/invocation.rb:134:in `map'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/invocation.rb:134:in `invoke_all'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/group.rb:232:in `dispatch'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/base.rb:485:in `start'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/rails-ca3e163b7cf8/railties/lib/rails/generators.rb:275:in `invoke'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/rails-ca3e163b7cf8/railties/lib/rails/commands/generate/generate_command.rb:26:in `perform'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/rails-ca3e163b7cf8/railties/lib/rails/command/base.rb:69:in `perform'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/rails-ca3e163b7cf8/railties/lib/rails/command.rb:48:in `invoke'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/rails-ca3e163b7cf8/railties/lib/rails/commands.rb:18:in `<main>'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/bootsnap-1.13.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/bootsnap-1.13.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/gems/skylight-5.3.4/lib/skylight/probes.rb:166:in `require'
	from /Users/louim/work/didacte/bin/rails:5:in `<main>'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/rails-ca3e163b7cf8/activesupport/lib/active_support/fork_tracker.rb:10:in `block in fork'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/rails-ca3e163b7cf8/activesupport/lib/active_support/fork_tracker.rb:10:in `block in fork'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/rails-ca3e163b7cf8/activesupport/lib/active_support/fork_tracker.rb:8:in `fork'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/rails-ca3e163b7cf8/activesupport/lib/active_support/fork_tracker.rb:8:in `fork'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/rails-ca3e163b7cf8/activesupport/lib/active_support/fork_tracker.rb:27:in `fork'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/rails-ca3e163b7cf8/activesupport/lib/active_support/fork_tracker.rb:8:in `fork'
	from /Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/gems/3.1.0/bundler/gems/rails-ca3e163b7cf8/activesupport/lib/active_support/fork_tracker.rb:27:in `fork'
	from <internal:/Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/3.1.0/rubygems/core_ext/kernel_require.rb>:85:in `require'
	from <internal:/Users/louim/.asdf/installs/ruby/3.1.3/lib/ruby/3.1.0/rubygems/core_ext/kernel_require.rb>:85:in `require'
	from -e:1:in `<main>'

But the source is checked out and I'm able to start the application without any problem. I'm using the latest version (ATM) of the gem: 0.43. The problem is not specific to that gem, if commented from the file, it fails on the next one using git.

Happy to help debugging if you need more details!

Getting error `/usr/bin/env: 'ruby\r': no such file or directory` in Windows

I'm building a project using a template available in this repo

Now, someone tried running the project in Windows using docker desktop and they keep getting this error:

#17 110.5 Installing sprockets-rails 3.4.2 #17 110.6 Installing actionmailer 7.0.4.3 #17 110.7 Installing activestorage 7.0.4.3 #17 110.7 Installing railties 7.0.4.3 #17 111.1 Fetching actionmailbox 7.0.4.3 #17 111.1 Fetching actiontext 7.0.4.3 #17 111.3 Installing actionmailbox 7.0.4.3 #17 111.3 Installing actiontext 7.0.4.3 #17 112.1 Fetching cssbundling-rails 1.1.2 #17 112.1 Fetching jsbundling-rails 1.1.1 #17 112.1 Fetching turbo-rails 1.4.0
#17 112.1 Fetching stimulus-rails 1.2.1 #17 112.3 Installing cssbundling-rails 1.1.2 #17 112.3 Installing jsbundling-rails 1.1.1 #17 112.4 Installing stimulus-rails 1.2.1
#17 112.5 Installing turbo-rails 1.4.0
#17 114.7 Fetching actioncable 7.0.4.3
#17 114.7 Fetching puma 5.6.5
#17 114.9 Installing actioncable 7.0.4.3
#17 115.0 Installing puma 5.6.5 with native extensions
#17 115.1 Fetching rails 7.0.4.3
#17 115.3 Installing rails 7.0.4.3
#17 116.3 Fetching bootsnap 1.16.0
#17 116.4 Installing bootsnap 1.16.0 with native extensions
#17 127.0 Bundle complete! 18 Gemfile dependencies, 57 gems now installed.
#17 127.0 Gems in the groups 'development' and 'test' were not installed.
#17 127.0 Bundled gems are installed into `./vendor/bundle`
#17 DONE 130.2s
#18 [build 5/9] COPY --link package.json yarn.lock ./
# 18 DONE 0.1s
#19 [build 6/9] RUN yarn install --frozen-lockfile
#19 1.546 yarn install v1.22.19
#19 1.657 [1/4] Resolving packages...
#19 1.809 [2/4] Fetching packages...
#19 27.33 [3/4] Linking dependencies...
# 19 29.95 [4/4] Building fresh packages...
# 19 30.50 Done in 28.96s.
#19 DONE 30.7s
#20 [build 7/9] COPY --link . .
#20 DONE 0.2s
#21 [build 8/9] RUN bundle exec bootsnap precompile app/lib/
# 21 DONE 1.7s
#22 [build 9/9] RUN SECRET_KEY_BASE=DUMMY ./bin/rails assets: precompile
#22 0.692 /usr/bin/env: 'ruby\r': No such file or directory
#22 ERROR: executor failed running [/bin/sh -c SECRET_KEY_BASE=DUMMY ./bin/rails assets: precompile]: exit code: 127
> [build 9/9] RUN SECRET_KEY_BASE=DUMMY ./bin/rails assets: precompile:
#22 0.692 /usr/bin/env: 'ruby\r': No such file or directory
failed to solve: executor failed running [/bin/sh -c SECRET_KEY_BASE=DUMMY ./bin/rails assets: precompile]: exit code: 127 $

(sorry that I can't offer a more complete backtrace, it's what the person receiving the error sent me)

Here's the Dockerfile:

# syntax = docker/dockerfile:1

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

# Rails app lives here
WORKDIR /rails

# Set production environment
ENV RAILS_ENV="production" \
    BUNDLE_WITHOUT="development:test" \
    BUNDLE_DEPLOYMENT="1"

# 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=14.20.1
ARG YARN_VERSION=1.22.19
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 package.json yarn.lock ./
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 postgresql-client && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Run and own the application files as a non-root user for security
ARG UID=1000 \
    GID=1000
RUN groupadd -f -g $GID rails && \
    useradd -u $UID -g $GID rails --home /rails --shell /bin/bash
USER rails:rails

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

# 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"]

the docker-compose.yml

version: "3.8"
services:
  web:
    build:
      context: .
      args:
        UID: ${UID:-1000}
        GID: ${GID:-${UID:-1000}}
    ports:
      - "3000:3000"
    environment:
      - RAILS_MASTER_KEY=$RAILS_MASTER_KEY
      - DATABASE_URL=postgres://root:password@postgres-db/
    depends_on:
      postgres-db:
        condition: service_healthy

  postgres-db:
    image: postgres
    environment:
      POSTGRES_USER: root
      POSTGRES_PASSWORD: password
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: pg_isready
      interval: 2s
      timeout: 5s
      retries: 30

(we're using docker compose to build and run the images)

The problem is that I don't really have any windows environment to test it. It runs perfectly in different Linux environments, and I've read that the problem is directly related to windows line endings. The other problem, not a minor one, is that the user trying to run the project is not really technical, so I vastly prefer to understand how to make it work out-of-the-box instead of offering some solution that the affected person has to run on they end.

Use `bun` official images

Hey ๐Ÿ‘‹๐Ÿป
I've been using Bun for quite some time now, and it seems stable for use with Rails.
I was checking the Docker ecosystem for Bun and turns out that they have official images now. I toyed around a little bit and manage to use the official image instead of manually install Bun like this:

FROM oven/bun:1 as bun

# Install node modules
COPY --link package.json bun.lockb ./
RUN --mount=type=cache,id=bld-bun-cache,target=/root/.bun \
    bun install --frozen-lockfile


FROM prebuild as build

# Install application gems
COPY --link Gemfile Gemfile.lock .ruby-version ./
RUN --mount=type=cache,id=bld-gem-cache,sharing=locked,target=/srv/vendor \
    bundle config set app_config .bundle && \
    bundle config set path /srv/vendor && \
    bundle install && \
    bundle clean && \
    mkdir -p vendor && \
    bundle config set path vendor && \
    cp -ar /srv/vendor .

# Copy bun modules
COPY --from=bun /home/bun/app/node_modules /rails/node_modules
COPY --from=bun /usr/local/bin/bun /usr/local/bun
ENV PATH=/usr/local/bun/bin:$PATH

Changes needed were

- FROM prebuild as bun
+ FROM oven/bun:1 as bun # it could point to version and OS like `1-alpine` and so on

# on the prebuild stage

- COPY --from=bun /rails/node_modules /rails/node_modules
+ COPY --from=bun /home/bun/app/node_modules /rails/node_modules

- COPY --from=bun /usr/local/bun /usr/local/bun
+ COPY --from=bun /usr/local/bin/bun /usr/local/bun/bin/.

what are your thoughts on this one?

Add more clarity on deb packages being build or runtime dependencies

Consider adding a new option or more information in README about installing deb packages needed for gem compilation and those needed for runtime.

It's about a situation when the app needs a gem for Postgresql, MySQL and MSSQL server to work (as well as GEOS for geospatial extensions). In this case, simply using the -add option does not help, and you have to assign different packages in the section:

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 default-libmysqlclient-dev git libpq-dev node-gyp pkg-config python-is-python3 \
    freetds-dev libgeos-dev

than the packages needed here:

FROM base

# Install packages needed for deployment
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y \
    default-libmysqlclient-dev default-mysql-client freetds-bin libgeos-* libvips nodejs postgresql-client && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

Apparently --add only adds packages required to build gems.

I am also curious if this tool is to be used to create Docker images for development or is it intended to be only for building production images? I'm asking because here is hardcoded RAILS_ENV production

The esbuild-demo is failing: assets are not present in the asset pipeline.

I think the Dockerfile is missing the rails assets:precompile and because of that the assets are not included in the asset pipeline.

โฏ docker run -p 3000:3000 -e RAILS_MASTER_KEY=$(cat config/master.key) esbuild-welcome
W, [2023-01-22T00:16:02.288717 #7]  WARN -- : You are running SQLite in production, this is generally not recommended. You can disable this warning by setting "config.active_record.sqlite3_production_warning=false".
=> Booting Puma
=> Rails 7.0.4.1 application starting in production 
=> Run `bin/rails server --help` for more startup options
W, [2023-01-22T00:16:02.729948 #1]  WARN -- : You are running SQLite in production, this is generally not recommended. You can disable this warning by setting "config.active_record.sqlite3_production_warning=false".
Puma starting in single mode...
* Puma version: 5.6.5 (ruby 3.1.3-p185) ("Birdie's Version")
*  Min threads: 5
*  Max threads: 5
*  Environment: production
*          PID: 1
* Listening on http://0.0.0.0:3000
Use Ctrl-C to stop
I, [2023-01-22T00:16:09.640125 #1]  INFO -- : [51ad9596-d807-424d-a198-1f70350edc90] Started GET "/" for 172.17.0.1 at 2023-01-22 00:16:09 +0000
I, [2023-01-22T00:16:09.641340 #1]  INFO -- : [51ad9596-d807-424d-a198-1f70350edc90] Processing by TimeController#index as HTML
I, [2023-01-22T00:16:09.643353 #1]  INFO -- : [51ad9596-d807-424d-a198-1f70350edc90]   Rendered time/index.html.erb within layouts/application (Duration: 0.3ms | Allocations: 288)
I, [2023-01-22T00:16:09.645141 #1]  INFO -- : [51ad9596-d807-424d-a198-1f70350edc90]   Rendered layout layouts/application.html.erb (Duration: 2.1ms | Allocations: 1272)
I, [2023-01-22T00:16:09.645281 #1]  INFO -- : [51ad9596-d807-424d-a198-1f70350edc90] Completed 500 Internal Server Error in 4ms (Allocations: 2487)
F, [2023-01-22T00:16:09.645694 #1] FATAL -- : [51ad9596-d807-424d-a198-1f70350edc90]   
[51ad9596-d807-424d-a198-1f70350edc90] ActionView::Template::Error (The asset "application.css" is not present in the asset pipeline.
):
[51ad9596-d807-424d-a198-1f70350edc90]      6:     <%= csrf_meta_tags %>
[51ad9596-d807-424d-a198-1f70350edc90]      7:     <%= csp_meta_tag %>
[51ad9596-d807-424d-a198-1f70350edc90]      8: 
[51ad9596-d807-424d-a198-1f70350edc90]      9:     <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
[51ad9596-d807-424d-a198-1f70350edc90]     10:     <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
[51ad9596-d807-424d-a198-1f70350edc90]     11:   </head>
[51ad9596-d807-424d-a198-1f70350edc90]     12: 
[51ad9596-d807-424d-a198-1f70350edc90]   
[51ad9596-d807-424d-a198-1f70350edc90] app/views/layouts/application.html.erb:9

dockerfile_generator.rb:505:in `>=': comparison of Gem::Version with String failed (ArgumentError)

https://github.com/fly-apps/dockerfile-rails/blob/31c1d3de61e53cbd5f1ad4bc773b40cfcaf0d8a8/lib/generators/dockerfile_generator.rb#L504C1-L504C75

Support to compare versions using String is a recent addition, causing this line to raise exception RubyGems < 3.4.19

โ–ถ ruby --version
ruby 3.0.4p208 (2022-04-12 revision 3fa771dded) [x86_64-darwin20]

โ–ถ gem --version
3.2.33

โ–ถ bin/rails generate dockerfile --parallel --cache --ci
/Users/ttilberg/.asdf/installs/ruby/3.0.4/lib/ruby/gems/3.0.0/gems/dockerfile-rails-1.5.7/lib/generators/dockerfile_generator.rb:504:in `>=': comparison of Gem::Version with String failed (ArgumentError)
	from /Users/ttilberg/.asdf/installs/ruby/3.0.4/lib/ruby/gems/3.0.0/gems/dockerfile-rails-1.5.7/lib/generators/dockerfile_generator.rb:504:in `base_gems'

1.5.2 Dockerfile breaks on ECS

I upgraded a project from 1.5.1 to 1.5.2 and generated a new Dockerfile. Subsequently, nginx refuses to start:

July 29, 2023 at 09:01 (UTC+2:00) 07:01:17 rails.1 | terminated by SIGTERM  staging-web
July 29, 2023 at 09:01 (UTC+2:00) 07:01:17 nginx.1 | exited with code 1 staging-web
July 29, 2023 at 09:01 (UTC+2:00) 07:01:17 system | sending SIGTERM to all processes  staging-web
July 29, 2023 at 09:01 (UTC+2:00) 07:01:16 nginx.1 | 2023/07/29 07:01:16 [emerg] 20#20: open() "/usr/share/nginx/stdout" failed (13: Permission denied) staging-web
July 29, 2023 at 09:01 (UTC+2:00) 07:01:16 nginx.1 | 2023/07/29 07:01:16 [warn] 20#20: the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:1  staging-web
July 29, 2023 at 09:01 (UTC+2:00) 07:01:16 nginx.1 | started with pid 14  staging-web
July 29, 2023 at 09:01 (UTC+2:00) 07:01:16 rails.1 | started with pid 15

This is on AWS ECS. For good measure, the Dockerfile diff:

diff --git a/Dockerfile b/Dockerfile
index c7220c5..06a329e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -51,14 +51,14 @@ RUN apt-get update -qq && \
 # configure nginx
 RUN gem install foreman && \
     sed -i 's|pid /run|pid /rails/tmp/pids|' /etc/nginx/nginx.conf && \
-    sed -i 's/access_log\s.*;/access_log \/dev\/stdout;/' /etc/nginx/nginx.conf && \
-    sed -i 's/error_log\s.*;/error_log \/dev\/stderr info;/' /etc/nginx/nginx.conf
+    sed -i 's/access_log\s.*;/access_log stdout;/' /etc/nginx/nginx.conf && \
+    sed -i 's/error_log\s.*;/error_log stderr info;/' /etc/nginx/nginx.conf

 COPY <<-"EOF" /etc/nginx/sites-available/default
 server {
   listen 3000 default_server;
   listen [::]:3000 default_server;
-  access_log /dev/stdout;
+  access_log stdout;

   root /rails/public;

@@ -92,7 +92,7 @@ RUN useradd rails --create-home --shell /bin/bash && \
 USER rails:rails

 # Deployment options
-ENV LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libjemalloc.so.2" \
+ENV LD_PRELOAD="libjemalloc.so.2" \
     MALLOC_CONF="dirty_decay_ms:1000,narenas:2,background_thread:true" \
     PORT="3001" \
     RAILS_LOG_TO_STDOUT="1"

Expose BUNDLE_WITHOUT as ARG to enable building test and production images

I think the old fly.io images also allowed this.

This allows me to build the image with BUNDLE_WITHOUT=development
This would be the image to use in the CI pipeline for testing.
After all tests were successful, one would build the production image with BUNDLE_WITHOUT=development:test.

ARG BUNDLE_WITHOUT=development:test
ENV BUNDLE_WITHOUT ${BUNDLE_WITHOUT}

`git` package does not properly resolve

Proposal

Always include git in the official Rails Dockerfile.

Rationale

Even if a Gemfile starts off without a git reference, it's something that is likely to happen during the course of a Rails project. It would be great to not have this break the Dockerfile build.

Bug

Everything below is a bug I ran into when deploying a Rails application with a github reference. I can dive deeper into the problem, but the simpler fix with what I think is a better dev experience is what I propose above.


When I deploy an application with the following Gemfile:

source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 7.0.0'
# Use Puma as the app server
gem 'puma', '~> 6.0'
# Use SCSS Gor stylesheets
gem 'sassc-rails'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.10'
# Use Redis adapter to run Action Cable in production
gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.13'
# HAML templates yo
gem 'haml-rails'
# Authorization libraries
gem 'omniauth'
gem 'omniauth-google-oauth2'
# Authorization logic
gem 'pundit'
# Form helper
gem 'simple_form', '~> 5.0'
# Communicate with Twilio
gem 'twilio-ruby'
# Handle phone numbers as a propper data type (and normalize)
gem 'phony_rails'
# For claims to create resources in a phone ack
gem 'jwt'
# Validate password and token strengths based on entropy.
gem 'strong_password'
# Display times locally
gem 'local_time'
# Run Postgres in production
gem 'pg', '~> 1.4'
# Application error alerts
gem 'rollbar'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false
# Generate's PDFs, mostly for labels
gem "prawn", "~> 2.0"
# Print QR codes in a PDF
gem "prawn-qrcode", "~> 0.5.0"
# Hierarchial record organization in ActiveRecord
gem "closure_tree", "~> 7.0"
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
# Parse natrual language date and time strings
gem "chronic", "~> 0.10.2"
# Search Postgres records
gem "pg_search", "~> 2.3"
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 5.3'
# Blanks from forms are converted into nil instead of stored as "" in the database
gem "nilify_blanks", "~> 1.4"
# Does union queries in ActiveRecord.
gem "active_record_union"
# Converts GUIDs in the URLs into shorter ids
gem "anybase", "~> 0.0.15"
# Pull this out when Rails 6.1 is a thing
gem "view_component", "~> 2.0"
# View template for CSV files
gem "csv_builder", "~> 2.1"
# Content management
if sitepress_gem_path = ENV["SITEPRESS_GEM_PATH"]
  gem "sitepress",        path: sitepress_gem_path
  gem "sitepress-cli",    path: sitepress_gem_path
  gem "sitepress-rails",  path: sitepress_gem_path
  gem "sitepress-core",   path: sitepress_gem_path
  gem "sitepress-server", path: sitepress_gem_path
else
  gem "sitepress-rails", github: "sitepress/sitepress", branch: "main"
end

# Markdown gem
gem "redcarpet", "~> 3.5.0"
# Parses SVG vector files and inverts them for darkmode.
gem "color", "~> 1.8"
# Used for reporting from the shell so I can see who signed up within a day/week
gem "groupdate", "~> 5.2"
# Render PDF file as an image to get around really annoying problems
# embedding PDFs in webpages or trying to embed them via PDF.js
gem "mini_magick", "~> 4.0.0"

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'rspec-rails'
  gem 'faker'
  gem 'factory_bot_rails'
end

group :development do
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
  gem 'spring-commands-rspec'
end

group :test do
  gem 'pundit-matchers', '~> 1.7.0'
  gem 'shoulda-matchers', '~> 4.5.0'
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of web drivers to run system tests with browsers
  gem 'webdrivers'
  gem 'rexml'
end

if featureomatic_path = ENV["FEATUREOMATIC_GEM_PATH"]
  gem "featureomatic", path: featureomatic_path
else
  gem "featureomatic", "~> 0.1.1", github: "rocketshipio/featureomatic"
end

gem "imageomatic", "~> 0.1.1", github: "imageomatic/ruby"

gem "mailto", "~> 0.1.1"

gem "stripe", "~> 5.38"

# Resource-oriented rails controllers
if oxidizer_peth = ENV["OXIDIZER_GEM_PATH"]
  gem "oxidizer", path: oxidizer_peth
else
  gem "oxidizer", github: "rocketshipio/oxidizer", branch: "main"
end

# No-password login flow
gem "nopassword", github: "rocketshipio/nopassword", branch: "main"
gem "matrix", "~> 0.4.2"

gem "ahoy_matey", "~> 4.1"

gem "slim-rails", "~> 3.5"

gem "blazer", "~> 2.6"

gem "turbo-rails", "~> 1.3"

gem "dockerfile-rails", ">= 1.0.0", :group => :development

The git package is not included.

I've tracked down where that's set at https://github.com//rubys/dockerfile-rails/blob/f8564073360444cfd967eeb6c3d7f08c6ac29615/lib/dockerfile-rails/scanner.rb#L30-L42

What I'm not sure about is if the rescue block is catching "too much" since it rescues all errors. Here's what I get when I run that block in irb or bundle exec irb

irb(main):007:0> gemfile_definition.spec_git_paths.empty?
/Users/bradgessler/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/bundler-2.3.16/lib/bundler/source/git/git_proxy.rb:224:in `allowed_with_path': The git source https://github.com/rocketshipio/nopassword.git is not yet checked out. Please run `bundle install` before trying to start your application (Bundler::GitError)
	from /Users/bradgessler/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/bundler-2.3.16/lib/bundler/source/git/git_proxy.rb:190:in `find_local_revision'
	from /Users/bradgessler/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/bundler-2.3.16/lib/bundler/source/git/git_proxy.rb:62:in `revision'
	from /Users/bradgessler/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/bundler-2.3.16/lib/bundler/source/git.rb:234:in `revision'
	from /Users/bradgessler/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/bundler-2.3.16/lib/bundler/source/git.rb:103:in `install_path'
	from /Users/bradgessler/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/bundler-2.3.16/lib/bundler/definition.rb:274:in `block in spec_git_paths'
	from /Users/bradgessler/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/bundler-2.3.16/lib/bundler/definition.rb:274:in `map'
	from /Users/bradgessler/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/bundler-2.3.16/lib/bundler/definition.rb:274:in `spec_git_paths'
	from (irb):7:in `<main>'
	from /Users/bradgessler/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/irb-1.6.2/exe/irb:11:in `<top (required)>'
	from /Users/bradgessler/.rbenv/versions/3.2.0/bin/irb:25:in `load'
	from /Users/bradgessler/.rbenv/versions/3.2.0/bin/irb:25:in `<main>'
irb(main):008:1*       if File.exist? 'Gemfile'
irb(main):009:2*         begin
irb(main):010:2*           gemfile_definition = Bundler::Definition.build('Gemfile', nil, [])
irb(main):011:2*           @gemfile += gemfile_definition.dependencies.map(&:name)
irb(main):012:2*
irb(main):013:3*           unless ENV['RAILS_ENV'] == 'test'
irb(main):014:3*             @git = !gemfile_definition.spec_git_paths.empty?
irb(main):015:2*           end
irb(main):016:2*         rescue => error
irb(main):017:2*           STDERR.puts error.message
irb(main):018:1*         end
irb(main):019:0>       end
irb(main):020:0*
undefined method `+' for nil:NilClass
=> nil

`docker-compose.yml` for `postgresql` specifies a local volume that ends up being owned by `root` on the host

Disclaimer: I'm not a docker superuser, especially when it comes to managing permissions and users between the host and containers, so it's very possible I'm doing something wrong.

Doing a standard generation with --compose --postgresql seems to generate a docker-compose.yml that specifies a volume mapping of ./tmp/db:/var/lib/postgresql/data; the resulting tmp/db directory is owned by root on the host system which is problematic - this caught me out specifically because when I attempted to do docker build . it would fail since it wasn't allowed to read that file.

I suspect this is probably not a direct bug in the gem itself, but it's the sort of thing I feel this gem should be trying to help with.

(I assume this probably happened with all variations that configure volumes, but have only used postgresql myself)

Will there be multiple db migrations running if multiple rails servers started?

So the dockerfile has this line

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

It will run migration when rails server being started. My question is, if auto-scaling triggers multiple web instances, will that run multiple db migrations at the same time? I think it would be nice if the migration could only run just once, after deployment, not everytime when a web server starts.

`packages` directive on `dockerfile.yaml` doesn't install all packages

I have a config file like this:

# generated by dockerfile-rails

---
options:
  bin-cd: true
  cache: true
  fullstaq: true
  jemalloc: true
  parallel: true
  postgresql: true
  redis: true
  root: true
  yjit: true
  packages:
    base:
    - git
    - libyaml-dev
  envs:
    base:
      BUNDLE_WITHOUT: development:test:linting:profiler
      GIT_REV: "${GIT_REV}"
      BUILD_DATE: "${BUILD_DATE}"
      RAILS_LOG_TO_STDOUT: 'true'
  args:
    base:
      GIT_REV: "${GIT_REV}"
      BUILD_DATE: "${BUILD_DATE}"

when I try to generate the Dockerfile, libyaml-dev is simply ignored, no matter what I try and if I manually add it to the Dockerfile, it gets wiped if I try to regenerate the Dockerfile.

I'm not sure if it is related or not but I'm using Rails from the main branch and when I run the generator, I see this before anything else:

The git source https://github.com/rails/rails.git is not yet checked out. Please run `bundle install` before trying to start your application
   identical  Dockerfile
   identical  .dockerignore
   identical  .node-version
   identical  bin/docker-entrypoint
   identical  config/dockerfile.yml

even though the git source is definitely checked out

Change default ports when Thruster is present

Over the weekend, I realized that the exposed port dockerfile-rails generated when Thruster is present is insufficient to make Thurster work transparently. I summed up my findings here but long story short:

  • Thruster's default port is 80
  • Thruster's default Puma port is 3000
  • dockerfile-rails exposes 3000
  • Services like Traefik, which checks for EXPOSE directives to route traffic to the correct port, will bypass Thruster completely in these cases

I think the easiest solution is to keep exposing 3000 for the sake of backward compatibility (and everyone expects Rails to expose 3000 anyway) but bind Thruster to 3000 and Puma to e.g. 3001, something like this:

if thurster?
 ARG TARGET_PORT=3001
 ARG HTTP_PORT=3000
 
 EXPOSE ${HTTP_PORT}
end

I can take a stab at a PR for it unless you have a better idea

Add curl

I understand this isn't meant for mrsk, but it'll be used by many people for that. And mrsk now requires curl to be installed.

basecamp/kamal#276

Maybe add it by default (it's a small package) or via an option?

Node path not found error when building image with Alpine

Hi, I'm trying to generate Dockerfile with Alpine:

$ bin/rails generate dockerfile --alpine --jemalloc --yjit --cache --parallel

Node part of the generated Dockfile:

# Install JavaScript dependencies
ARG NODE_VERSION=20.10.0
ARG YARN_VERSION=1.22.21
RUN curl -sL https://unofficial-builds.nodejs.org/download/release/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64-musl.tar.gz | tar xz -C /tmp/ && \
    cp -rp /tmp/node-v${NODE_VERSION}-linux-x64-musl/* /usr/local && \
    npm install -g yarn@$YARN_VERSION && \
    rm -rf /tmp/node-v${NODE_VERSION}-linux-x64-musl

# Copy node modules
COPY --from=node /rails/node_modules /rails/node_modules
COPY --from=node /usr/local/node /usr/local/node
ENV PATH=/usr/local/node/bin:$PATH

When building the image, it raises an error:
Error: failed to fetch an image or build from source: error building: failed to solve: failed to compute cache key: "/usr/local/node" not found: not found

Found the reason is the Node paths are inconsistent:

  • cp -rp /tmp/node-v${NODE_VERSION}-linux-x64-musl/* /usr/local && \ use /usr/local
  • COPY --from=node /usr/local/node /usr/local/node use /usr/local/node

I tried to fix it by modifying the Dockerfile:

# Install JavaScript dependencies
ARG NODE_VERSION=20.10.0
ARG YARN_VERSION=1.22.21
+ ENV PATH=/usr/local/node/bin:$PATH
RUN curl -sL https://unofficial-builds.nodejs.org/download/release/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64-musl.tar.gz | tar xz -C /tmp/ && \
+    mkdir /usr/local/node && \
-    cp -rp /tmp/node-v${NODE_VERSION}-linux-x64-musl/* /usr/local && \
+    cp -rp /tmp/node-v${NODE_VERSION}-linux-x64-musl/* /usr/local/node && \
    npm install -g yarn@$YARN_VERSION && \
    rm -rf /tmp/node-v${NODE_VERSION}-linux-x64-musl

It works, but I'm not sure it's the best way.

So please fix it in the generator, thanks.

Support secrets for a private gemserver

I host some gems in a private gemserver.

As it seems, dockerfile-rails does not support to set secrets.

This is how I do it in my current Dockerfile:

RUN --mount=type=secret,id=gemserver_credentials,dst=/kaniko/gemserver_credentials \
  --mount=type=cache,id=gems,sharing=locked,target=/app/vendor/cache \
  BUNDLE_GEMS__MYSERVER__COM="$(cat /kaniko/gemserver_credentials)" \
  && mkdir -p /usr/local/bundle \
  && export BUNDLE_GEMS__MYSERVER__COM \
  && bundle config set cache_all true \
  && bundle install \
  && find /usr/local/bundle/ -name ".git" -exec rm -rv {} + \
  && find /usr/local/bundle/ -name "*.c" -delete \
  && find /usr/local/bundle/ -name "*.o" -delete \
  && bundle cache \
  && bundle config unset cache_all \
  && rm -rf /usr/local/bundle/ruby/*/cache

Notable is

  • I use env variables, as to pass the credentials to bundler
  • --mount=type=secret,id=gemserver_credentials,dst=/kaniko/gemserver_credentials I mount the secret at /kaniko to be able to support kaniko next to docker build
  • BUNDLE_GEMS__MYSERVER__COM="$(cat /kaniko/gemserver_credentials)" reads the secret
  • export BUNDLE_GEMS__MYSERVER__COM exposes the secret to bundler

I also have a special Docker Compose file, which I load in addition to the normal docker-compose.yml file

version: '3.8'

services:
  web:
    build:
      secrets:
        - gemserver_credentials
    secrets:
      - gemserver_credentials

secrets:
  gemserver_credentials:
    file: ./BUNDLE_GEMS__MYSERVER__COM.txt

`yarn: not found` when using `jsbundling-rails` with `--parallel` option

 > [build 7/7] RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile:
#21 3.323 sh: 1: yarn: not found
#21 3.323 rails aborted!
#21 3.324 jsbundling-rails: Command build failed, ensure yarn is installed and `yarn build` runs without errors
#21 3.324 /rails/vendor/ruby/3.2.0/gems/jsbundling-rails-1.1.1/lib/tasks/jsbundling/build.rake:5:in `block (2 levels) in <main>'
Dockerfile

# syntax = docker/dockerfile:1

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.2.1
FROM quay.io/evl.ms/fullstaq-ruby:${RUBY_VERSION}-jemalloc-slim as base

LABEL fly_launch_runtime="rails"

# Rails app lives here
WORKDIR /rails

# Set production environment
ENV RAILS_ENV="production" \
    BUNDLE_WITHOUT="development:test" \
    BUNDLE_DEPLOYMENT="1"

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


# Throw-away build stages to reduce size of final image
FROM base as prebuild

# Install packages needed to build gems and node modules
RUN --mount=type=cache,id=dev-apt-cache,sharing=locked,target=/var/cache/apt \
    --mount=type=cache,id=dev-apt-lib,sharing=locked,target=/var/lib/apt \
    apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential curl git node-gyp pkg-config python-is-python3


FROM prebuild as node

# Install JavaScript dependencies
ARG NODE_VERSION=16.15.1
ARG YARN_VERSION=1.22.18
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 node modules
COPY --link package.json yarn.lock ./
RUN --mount=type=cache,id=bld-yarn-cache,target=/root/.yarn \
    YARN_CACHE_FOLDER=/root/.yarn yarn install --frozen-lockfile


FROM prebuild as build

# Install application gems
COPY --link Gemfile Gemfile.lock ./
RUN --mount=type=cache,id=bld-gem-cache,sharing=locked,target=/srv/vendor \
    bundle config set app_config .bundle && \
    bundle config set path /srv/vendor && \
    bundle install && \
    bundle exec bootsnap precompile --gemfile && \
    bundle clean && \
    mkdir -p vendor && \
    bundle config set path vendor && \
    cp -ar /srv/vendor .

# Copy node modules
COPY --from=node /rails/node_modules /rails/node_modules

# Copy application code
COPY --link . .

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

# Adjust binfiles to set current working directory
RUN grep -l '#!/usr/bin/env ruby' /rails/bin/* | xargs sed -i '/^#!/aDir.chdir File.expand_path("..", __dir__)'

# 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 --mount=type=cache,id=dev-apt-cache,sharing=locked,target=/var/cache/apt \
    --mount=type=cache,id=dev-apt-lib,sharing=locked,target=/var/lib/apt \
    apt-get update -qq && \
    apt-get install --no-install-recommends -y iputils-ping libsqlite3-0 net-tools procps traceroute

# Run and own the application files as a non-root user for security
RUN useradd rails --home /rails --shell /bin/bash

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

# Deployment options
ENV 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"]

Spent some time to debug it, but maybe put some warning/confirmation when jsbundling-rails is present and user is using --parallel option or add node/yarn into prebuild stage?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.