Deploy Rails 7 with Docker and Nginx

roeland

Roeland

Posted on October 3, 2021

Deploy Rails 7 with Docker and Nginx

The easiest solution to deploy a Rails application is something like Heroku or Hatchbox or DigitalOcean Apps. But for some small projects I like to use my existing VPS.

I assume you have a VPS with Ubuntu 20.04 and Docker and Nginx installed.
If you use DigitalOcean you can select the Docker image in the marketplace and add Nginx to it.

New Rails 7 app

Lets create a new Rails project with PostgreSQL, esbuild and Tailwind on your local machine:

rails new demo -d postgresql --edge -j esbuild --css tailwind
Enter fullscreen mode Exit fullscreen mode

Adjust you config/database.yml with the settings for your database.

Scaffold a simple table:

bin/rails g scaffold Book name:string
Enter fullscreen mode Exit fullscreen mode

Then create your database and tables:

bin/rails db:create
bin/rails db:migrate
Enter fullscreen mode Exit fullscreen mode

You can create a root path in routes.rb:

root "books#index"
Enter fullscreen mode Exit fullscreen mode

Now you can start the website with:

bin/dev
Enter fullscreen mode Exit fullscreen mode

Docker

Let's go to the VPS. I transfer my code with Github.

For large projects you would probably use CI, but this is just a small project.

I prefer to create a small shell script to do the build steps and start rails.
This is the content of bin/prod:

#!/usr/bin/env bash
export RAILS_ENV=production
bundle install
yarn install
yarn build
yarn build:css
bin/rails assets:precompile
bin/rails server -b 0.0.0.0
Enter fullscreen mode Exit fullscreen mode

Let's make it executable with: chmod a+x bin/prod

Now create a Dockerfile:

FROM ruby:3
RUN apt-get update -qq && apt-get install -y nodejs npm postgresql-client
RUN npm install -g yarn
RUN gem update --system

# use a global path instead of vendor
ENV GEM_HOME="/usr/local/bundle"
ENV BUNDLE_PATH="$GEM_HOME"
ENV BUNDLE_SILENCE_ROOT_WARNING=1
ENV BUNDLE_APP_CONFIG="$GEM_HOME"
ENV PATH="$GEM_HOME/bin:$BUNDLE_PATH/gems/bin:${PATH}"

# make 'docker logs' work
ENV RAILS_LOG_TO_STDOUT=true

# copy the source
WORKDIR /app
COPY . /app
RUN rm -f tmp/pids/server.pid
RUN bundle install

# build and start
CMD ["bin/prod"]
Enter fullscreen mode Exit fullscreen mode

The master.key file is not in git for safety.
There are several solutions for this, but I just recreate the file on the server:

echo "30acf9tralalalalala7af75eb7" > config/master.key
Enter fullscreen mode Exit fullscreen mode

Now it's time to create the docker image.

Run this inside the root folder of the demo project:

docker build -t demo:0.0.1 .
Enter fullscreen mode Exit fullscreen mode

You should now see the image with docker images.

Lets run it:

docker run -d -p 3001:3000 --name demo --env RAILS_ENV=production -v ~/demo:/app demo:0.0.1
Enter fullscreen mode Exit fullscreen mode

The docker container is exposing port 3000, but I map that to 3001 since I already have an other website running on port 3000.

You should probably have a seperate Postgres server, but I also run that inside a Docker.
To allow access to this container I create a seperate network and add the two containers in it.
In database.yml you can than use postgres_container as host.

docker network create demo_network
docker network connect demo_network demo
docker network connect demo_network postgres_container
Enter fullscreen mode Exit fullscreen mode

To create the database and tables:

docker exec demo bin/rails db:create
docker exec demo bin/rails db:migrate
Enter fullscreen mode Exit fullscreen mode

In case of errors you can use docker logs demo to find the error.

Nginx

I use Nginx as a proxy to the different Rails projects and to load the assets directly.
To create a new configuration:

sudo vi /etc/nginx/sites-available/demo
Enter fullscreen mode Exit fullscreen mode

And this is the content: (you need to change the domain and paths)

upstream demo {
  server localhost:3001;
}

server {
  server_name demo.example.org;

  root /home/user/demo/public;
  access_log /home/user/demo/log/nginx.access.log;
  error_log /home/user/demo/log/nginx.error.log info;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  try_files $uri/index.html $uri @demo;
  location @demo {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header  X-Forwarded-Proto $scheme;
    proxy_set_header  X-Forwarded-Ssl on; # Optional
    proxy_set_header  X-Forwarded-Port $server_port;
    proxy_set_header  X-Forwarded-Host $host;

    proxy_redirect off;

    proxy_pass http://demo;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 100M;
  keepalive_timeout 10;
}
Enter fullscreen mode Exit fullscreen mode

Enable it:

sudo ln -s /etc/nginx/sites-available/demo /etc/nginx/sites-enabled/demo
Enter fullscreen mode Exit fullscreen mode

Test the configuration:

sudo nginx -t
Enter fullscreen mode Exit fullscreen mode

And restart Nginx:

sudo systemctl restart nginx
Enter fullscreen mode Exit fullscreen mode

You should now have a working website.
It's a good idea to add Let's Encrypt with the certbot tool.
This is explained here.

💖 💪 🙅 🚩
roeland
Roeland

Posted on October 3, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related