(2021) Docker + Ruby 3 + Rails 6 + Puma + Nginx + Postgres

João Scotto
4 min readFeb 19, 2021

This post is my solution to create a Rails project with Nginx reverse proxy. I found different contents on how to do it, but it was out of date. It’s probably not the best way to do this, but I hope I can help you in some way.

This post was based on itnext, thanks Satendra Rai.

Docker ps

So what will we do?

Dockerize a Rails application with the latest version of:

  • Ruby 3
  • Rails 6
  • Postgres 13
  • Puma
  • Nginx
  • Docker
  • Docker compose

Let’s get started

We need two docker containers, one for application and one for Nginx. If you don’t have a rails project, one option is to create through this post. But I recommend that you clone this repository which is a complete example.

Dockerize the application

Create the app.Dockerfile file in your project root directory, it does:

  • uses base image Ruby 3.0
  • install Node 14 LTS
  • install Yarn
  • install gems
  • precompile your assets
  • and start with puma server
FROM ruby:3.0.0# Install node 14-LTS and yarn
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt-get update -qq && apt-get install -qq --no-install-recommends \
nodejs \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN npm install -g yarn@1
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app
EXPOSE 3000RUN SECRET_KEY_BASE=1 RAILS_ENV=production bundle exec rake assets:precompile
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

Dockerize the Nginx

We will use nginx as a reverse proxy. Create the nginx.Dockerfile file in your project root directory.

FROM nginxRUN apt-get update -qq && apt-get -y install apache2-utilsENV RAILS_ROOT /app
WORKDIR $RAILS_ROOT
RUN mkdir log
COPY public public/
COPY nginx.conf /tmp/docker.nginx
RUN envsubst '$RAILS_ROOT' < /tmp/docker.nginx > /etc/nginx/conf.d/default.confEXPOSE 80
CMD [ "nginx", "-g", "daemon off;" ]

Configure nginx

Create the nginx.conf file in your project root directory.

Maybe you are wondering how he made this configuration file? ahah, I asked myself. This configuration was based on the official puma documentation, see here.

upstream app {
server 'app:3000';
}
server {
listen 80;
server_name localhost;
# ~2 seconds is often enough for most folks to parse HTML/CSS and
# retrieve needed images/icons/frames, connections are cheap in
# nginx so increasing this is generally safe...
keepalive_timeout 5;
# path for static files
root /app/public;
access_log /app/log/nginx.access.log;
error_log /app/log/nginx.error.log info;
# this rewrites all the requests to the maintenance.html
# page if it exists in the doc root. This is for capistrano's
# disable web task
if (-f $document_root/maintenance.html) {
rewrite ^(.*)$ /maintenance.html last;
break;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
# If the file exists as a static file serve it directly without
# running all the other rewrite tests on it
if (-f $request_filename) {
break;
}
# check for index.html for directory index
# if it's there on the filesystem then rewrite
# the url to add /index.html to the end of it
# and then break to send it to the next config rules.
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
# this is the meat of the rack page caching config
# it adds .html to the end of the url and then checks
# the filesystem for that file. If it exists, then we
# rewrite the url to have explicit .html on the end
# and then send it on its way to the next config rule.
# if there is no file on the fs then it sets all the
# necessary headers and proxies to our upstream pumas
if (-f $request_filename.html) {
rewrite (.*) $1.html break;
}
if (!-f $request_filename) {
proxy_pass http://app;
break;
}
}
# Now this supposedly should work as it gets the filenames with querystrings that Rails provides.
# BUT there's a chance it could break the ajax calls.
location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ {
expires max;
break;
}
# Error pages
# error_page 500 502 503 504 /500.html;
location = /500.html {
root /app/current/public;
}
}

The last step: Docker-compose!

Use the docker-compose to run docker containers together; in this example has:

  • Rails application
  • Postgres
  • Nginx

Create the docker-compose.yml file in your project root directory.

version: "3.8"
services:
postgres:
image: postgres:13-alpine
command: ["postgres", "-c", "fsync=false", "-c", "full_page_writes=off"]
environment:
POSTGRES_PASSWORD: password
volumes:
- ./tmp/db:/var/lib/postgresql/data
ports:
- "5432:5432"
app:
build:
context: .
dockerfile: app.Dockerfile
command: bash -c "bundle exec puma -C config/puma.rb"
volumes:
- .:/app
depends_on:
- postgres
nginx:
build:
context: .
dockerfile: ./nginx.Dockerfile
depends_on:
- app
ports:
- 80:80

Conclusion

this is NOT a step-by-step tutorial, it’s just my notes. You can see the full code at https://github.com/joaoscotto/docker-ruby-puma-nginx-postgres

Good luck!

--

--