These are the configurations I use to deploy a Rails 6 app. Note that this is a small hobby app so I’m using
docker-compose to deploy it, for more serious apps you’d want to use
docker swarm or
If you don’t have a server ready or
docker-compose installed on your local dev machine then have a read through this blog post first.
For this build I’m not going to dockerize NGINX as I have it installed on the server.
Create a Dockerfile
Step one is to create a
Dockerfile in the root directory of your project. For this build I’ll be using
alpine is a much slimmer Linux docker image and will reduce our overall image sizes.
# Extend from the official Ruby image FROM ruby:2.6.5-alpine3.11 ENV RAILS_ENV production ENV RAILS_SERVE_STATIC_FILES true ENV RAILS_LOG_TO_STDOUT true # Install required libraries on Alpine # note: build-base required for nokogiri gem # note: postgresql-dv required for pg gem RUN apk update && apk upgrade && \ apk add tzdata postgresql-dev && \ apk add postgresql-client && \ apk add nodejs yarn && \ apk add build-base # Throw errors if Gemfile has been modified since Gemfile.lock RUN bundle config --global frozen 1 # Copy Gemfile so we can cache gems COPY Gemfile Gemfile.lock ./ # Fix issue with sassc gem RUN bundle config --local build.sassc --disable-march-tune-native # Install Ruby gems RUN bundle install --without development test # Copy all application files COPY . . # Precompile assets RUN SECRET_KEY_BASE=`bundle exec rails secret` bundle exec rails assets:precompile # Run entrypoint.sh script RUN chmod +x entrypoint.sh CMD ["/entrypoint.sh"]
In order to precompile the assets during the image build a
SECRET_KEY_BASE needs to be set. We don’t want to hard-code a value into our Dockerfile though as we’ll set this later via an environment variable, so this is a “hack” to get the build to pass. If you know of a better way please let me know.
Note: if you’re only planning to use this setup to test your app locally then don’t set the
Note that you’ll only need the
RUN bundle config --local build.sassc --disable-march-tune-native line if you’re running into the following error:
/usr/local/bundle/gems/ffi-1.11.1/lib/ffi/library.rb:112: at 0x00007fdf4c5d7575 ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux-musl] -- Control frame information ----------------------------------------------- c:0084 p:---- s:0473 e:000472 CFUNC :open c:0083 p:0022 s:0467 e:000466 BLOCK /usr/local/bundle/gems/ffi-1.11.1/lib/ffi/library.rb:112 [FINISH] c:0082 p:---- s:0458 e:000457 CFUNC :each c:0081 p:0113 s:0454 e:000453 BLOCK /usr/local/bundle/gems/ffi-1.11.1/lib/ffi/library.rb:109 [FINISH] c:0080 p:---- s:0447 e:000446 CFUNC :map c:0079 p:0069 s:0443 e:000442 METHOD /usr/local/bundle/gems/ffi-1.11.1/lib/ffi/library.rb:99 c:0078 p:0079 s:0436 e:000435 CLASS /usr/local/bundle/gems/sassc-2.2.1/lib/sassc/native.rb:11 c:0077 p:0007 s:0432 e:000431 CLASS /usr/local/bundle/gems/sassc-2.2.1/lib/sassc/native.rb:6 c:0076 p:0014 s:0429 e:000428 TOP /usr/local/bundle/gems/sassc-2.2.1/lib/sassc/native.rb:5 [FINISH] ... [NOTE] You may have encountered a bug in the Ruby interpreter or extension libraries. Bug reports are welcome. For details: https://www.ruby-lang.org/bugreport.html Aborted (core dumped) /usr/local/bundle/gems/ffi-1.11.1/lib/ffi/library.rb:112: [BUG] Illegal instruction at 0x00007f128c9ec575 ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux-musl] ...
You can read more about this error here.
Create a docker-compose.yml file
Next create a
docker-compose.yml file in the root directory of your project.
To find out which version you can define at the top of your file check
docker -v and compare to the version reference here.
version: "3.7" services: rails: build: context: . env_file: - rails.env environment: DB_USERNAME: your_db_user DB_DATABASE: your_db_name DB_PORT: 5432 DB_HOST: db RAILS_MAX_THREADS: 5 PORT: 3000 ports: - 3000:3000 restart: always depends_on: - "db" db: image: postgres:10.12-alpine env_file: - db.env environment: POSTGRES_USER: your_db_user PGDATA: /var/lib/postgresql/data/pgdata restart: always volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata:
I’ve set both the
rails app and the
db (PostgreSQL) to always restart with
restart: always. If the container crashes or you reboot the server these services will start up automatically.
The Rails app depends on the database being up to run, we’ve set this with the
depends on: - db line. We’re also creating a
pgdata volume so the database data is persisted.
Note that we’re using an
env_file: to set some environment variables as well as
environment: for other variables. You can choose which variables you want committed to your version control system and which not to commit.
Create the .env files
Now we’ll create
db.env, you can name the files anything as long as they match the configuration in your
DB_PASSWORD=your_db_password SECRET_KEY_BASE=rails_secret_key # generate this with 'rails secret'
Add the .env files to your .gitignore
We don’t want to commit our secret keys to a version control system so add the following to your
# Ignore environment files *.env
Create a .dockerignore file
When copying our files into the
docker container we don’t want folders such as
node_modules to be copied. Also we don’t want any files with sensitive information such as
.env or our
master.key to be copied into the container.
You can use the file below as an example:
# Ignore bundler config. .bundle # Ignore all logfiles and tempfiles. log/ tmp/ # Ignore uploaded files in development. storage/ public/assets # Byebug history .byebug_history # Ignore master key for decrypting credentials and more. config/master.key # Node modules, yarn etc. public/packs public/packs-test node_modules yarn-error.log yarn-debug.log* .yarn-integrity # Ignore environment files *.env # Ignore database dumps *.dump # Misc files .dockerignore .git .cache
Create an entrypoint file
entrypoint.sh file in the project’s root directory:
#!/bin/sh # Note: !/bin/sh must be at the top of the line, # Alpine doesn't have bash so we need to use shell. # Docker entrypoint script. # Don't forget to give this file execution rights via `chmod +x entrypoint.sh` # which I've added to the Dockerfile but you could do this manually instead. # Wait until Postgres is ready before running the next step. while ! pg_isready -q -h $DB_HOST -p $DB_PORT -U $DB_USERNAME do echo "$(date) - waiting for database to start." sleep 2 done # If the database exists, migrate. Otherwise setup (create and migrate) echo "Running database migrations..." bundle exec rails db:migrate 2>/dev/null || bundle exec rails db:create db:migrate echo "Finished running database migrations." # Remove a potentially pre-existing server.pid for Rails. echo "Deleting server.pid file..." rm -f /tmp/pids/server.pid # Start the server. echo "Starting rails server..." bundle exec rails server
Although we’ve set
depends_on: - db in our
docker-compose.yml file, this only waits for the PostgreSQL container to run, however the database will still be starting up and we don’t want to try running any migrations etc. until it’s ready to accept connections hence the
while ! pg_isready line.
Note: Alpine doesn’t have
bashinstalled so make sure your
shellscript, or you can add
apk add bashto your
Shoutout: migrations check taken from here.
Configure the Rails app production settings
Before we can spin up the containers with
docker-compose up we also need to configure the app’s database in
production: <<: *default database: <%= ENV['DB_DATABASE'] %> username: <%= ENV['DB_USERNAME'] %> password: <%= ENV['DB_PASSWORD'] %> host: <%= ENV['DB_HOST'] %>
We’re mapping the environment variables to the ones we set in the
Note: if you’re only planning to use this setup to test your app locally then don’t change the
production settings in your
database.yml but the
development settings instead.
Testing the build in development
To test everything is working properly before deploying you can run
docker-compose up --build and visit
Note that this will run your app in
production mode so if you want a more elegant solution you can create multiple compose files and pass in your build arguments via the compose file.
Deploying the app
If you’re planning on deploying the app using docker-compose and docker contexts then follow this blog post. Again I only recommend this for smaller hobby apps, e.g. a personal blog, and not applications that require high availability and rolling upgrades - for that look to using
docker swarm or
If this is your first time containerizing an app then
docker-compose is a good way of getting started however.