Setup a Ruby on Rails 6 API project with Docker Compose
Here is a quick rundown on how to setup a Ruby on Rails 6 API project with Docker Compose.
- Prerequisites
- Create a Rails 6 API project
- Setup Docker
- Setup Docker Compose
- Start Rails Server
- Install RSpec (Optional)
- Troubleshooting
Prerequisites
Docker and Docker Compose
Docker: https://www.docker.com/get-started
Docker Compose: https://docs.docker.com/compose/install/
For example, I have:
$ docker -v
$ Docker version 19.03.2, build 6a30dfc$ docker-compose -v
$ docker-compose version 1.24.1, build 4667896b
Ruby
Rails 6 requires Ruby 2.5.0 or newer1.
If the local machine hasn't got Ruby installed, RVM would be a common solution to consider.
For installing RVM with default Ruby and Rails in one command, run:
\curl -sSL https://get.rvm.io | bash -s stable --rails
To verify the successful installation:
$ ruby -v
$ ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin18]$ rails -v
$ Rails 6.0.2.1
Create a Rails 6 API project
To create a new Ruby on Rails 6 API project, run:
rails new rails-6-api-docker-demo --api --database=postgresql -T -C
rails-6-api-docker-demo
: The name of the new project.--api
: Create an API only project.--database=postgresql
: Use PostgresQL as the default database adapter.-T
: (Optional) Skip test files. RSpec would be a more common option.-C
: (Optional) Skip ActionCable if no WebSockets is needed for the project.
Setup Docker
Create .dockerignore
cd rails-6-api-docker-demo
touch .dockerignore
.dockerignore
files essentially works the same way as .gitignore
, but for docker containers. You may just use the content from .gitignore
file plus the .git
folder and .gitignore
itself.
gitignore.io can be handy for generating the desired .gitignore
file. For example, https://www.gitignore.io/api/git,rails,rubymine gives an example of .gitignore
file for git, rails and rubymine IDE.
Here's a complete example: https://gist.github.com/yizeng/eeeb48d6823801061791cc5581f7e1fc
Create Dockerfile
touch Dockerfile
A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession.
Full documentation can be found here.
FROM ruby:alpine
RUN apk update && apk add bash build-base nodejs postgresql-dev tzdata
RUN mkdir /project
WORKDIR /project
COPY Gemfile Gemfile.lock ./
RUN gem install bundler --no-document
RUN bundle install --no-binstubs --jobs $(nproc) --retry 3
COPY . .
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]
FROM ruby:alpine
: Select the base image to build from. Here uses the latest alpine version of Ruby. Note that the version needs to match the Ruby version inGemfile
. If there is a mismatch (e.g.ruby '2.6.5'
inGemfile
), then here would beFROM ruby:2.6.5-alpine
. More versions can be found at the Docker Hub.RUN apk update && apk add build-base nodejs postgresql-dev tzdata
: Install the required packages inside Docker.RUN mkdir /project
: Create a folder calledproject
to host the codebase.WORKDIR /project
: Set the working directory toproject
folder.COPY Gemfile Gemfile.lock ./
: Copy the files to the working directory.RUN gem install bundler
: Install the bundler gem. It needs to match the version inGemfile.lock
, A specific version can be set likeRUN gem install bundler -v 2.0.2
.COPY . .
: Copy the codebase into Docker.CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]
: Set the start command.
Setup Docker Compose
Create docker-compose.yml
touch docker-compose.yml
Docker compose is a tool to build a multi-container application, where docker-compose.yml
is the YAML config file that tells Docker Compose how to build the containers together.
A more detailed documentation can be found here.
version: '3'
services:
db:
image: 'postgres:10-alpine'
volumes:
- 'postgres:/var/lib/postgresql/data'
ports:
- '5432:5432'
environment:
- POSTGRES_HOST_AUTH_METHOD=trust
redis:
image: 'redis:5-alpine'
command: redis-server
ports:
- '6379:6379'
volumes:
- 'redis:/data'
web:
depends_on:
- 'db'
- 'redis'
build: .
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
ports:
- '3000:3000'
environment:
- DATABASE_HOST=db
volumes:
redis:
postgres:
Here defines 3 services in order to run the Rails application:
db
: The PostgreSQL database service, which usespostgres:10-alpine
here for example. It maps port 5432 inside Docker to the same port on local host machine.redis
: The Redis caching service, which usesredis:5-alpine
here for example. It maps port 6379 inside Docker to the same port on local host machine. This is needed if Sidekiq is used for job processing or Rails caching is set to Redis instead of memcache.web
: This is how the Rails API is composed. It depends ondb
andredis
with port 3000 exposed on host machine.
Config database.yml
The default Ruby on Rails' database.yml
needs to be configed with the Docker database URL by adding host
, username
and password
to it.
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
host: <%= ENV.fetch("DATABASE_HOST") { "localhost" } %>
port: <%= ENV.fetch("DATABASE_PORT") { 5432 } %>
username: <%= ENV.fetch("DATABASE_USERNAME") { "postgres" } %>
password: <%= ENV.fetch("DATABASE_PASSWORD") { "" } %>
development:
<<: *default
database: rails_6_api_docker_demo_development
test:
<<: *default
database: rails_6_api_docker_demo_test
production:
<<: *default
database: rails_6_api_docker_demo_production
username: <%= ENV.fetch("DATABASE_USERNAME") { "rails_6_api_docker_demo" } %>
password: <%= ENV.fetch("DATABASE_PASSWORD") { "" } %>
Start Rails Server
Build the containers
docker-compose build
Before starting the server, Rails databases need to be created first.
docker-compose run web bundle exec rails db:create
docker-compose run web bundle exec rails db:migrate
Finally, to build and spin up the Rails 6 API server:
docker-compose up --build
Head over to http://localhost:3000 to test it out and start building the real API!
Install RSpec (Optional)
If the project was initialized with -T
option that no tests are generated, a testing framework would be needed for the project.
Here is how to add RSpec support.
-
Add rspec-rails to Gemfile
# Add it inside group :development, :test group :development, :test do # some existing gems... # ... gem 'rspec-rails' end
-
Rebuild the containers
docker-compose build
-
Install RSpec
docker-compose run web bundle exec rails generate rspec:install
-
Run the tests
docker-compose run web bundle exec rspec
Troubleshooting
Error starting userland proxy: listen tcp 0.0.0.0:6379: bind: address already in use
-
Reason: Port 6379 is already in use on local machine.This is most likely that there is another Redis server running locally.
-
Solution: Either stop the Redis on local machine or map to another port in
docker-compose.yml
, like- '6380:6379'
.
Error starting userland proxy: listen tcp 0.0.0.0:5432: bind: address already in use
-
Reason: Port 5432 is already in use on local machine. This is most likely that there is another PostgreSQL server running locally.
-
Solution: Either stop the PostgreSQL on local machine or map to another port in
docker-compose.yml
, like- '5434:5432'
.
Your Ruby version is 2.6.5, but your Gemfile specified 2.6.3
ERROR: Service 'web' failed to build: The command '/bin/sh -c bundle install' returned a non-zero code: 18
-
Reason: There is a Ruby version mismatch between
Gemfile
and Docker container. -
Solution: Either update the Ruby version in
Gemfile
to2.6.5
, or pull explicitly Ruby2.6.3
image inDockerfile
likeFROM ruby:2.6.3-alpine
.
/usr/local/bundle/ruby/2.6.0/gems/activesupport-6.0.2.1/lib/active_support/railtie.rb:39:in
rescue in block in <class:Railtie>': tzinfo-data is not present.
Please add gem 'tzinfo-data' to your Gemfile and run bundle install (TZInfo::DataSourceNotFound)
/usr/local/bundle/ruby/2.6.0/gems/tzinfo-1.2.5/lib/tzinfo/zoneinfo_data_source.rb:180:in `initialize':
None of the paths included in TZInfo::ZoneinfoDataSource.search_path are valid zoneinfo directories. (TZInfo::ZoneinfoDirectoryNotFound)
-
Reason: Required
tzdata
package was not installed in Docker container. -
Solution: Include
tzdata
inDockerfile
'sRUN apk add
command (see details above).
/usr/local/lib/ruby/2.6.0/rubygems.rb:283:in `find_spec_for_exe':
Could not find 'bundler' (2.0.2) required by your /project/Gemfile.lock. (Gem::GemNotFoundException)
ERROR: Service 'web' failed to build: The command '/bin/sh -c bundle install' returned a non-zero code: 1
-
Reason: Required gem bundler was not installed in Docker container.
-
Solution: Include
RUN gem install bundler
command inDockerfile
(see details above).
-
https://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#ruby-versions ↩