Love, docker-compose!
#Ruby #Rails #Postgres #Docker #Docker-compose
Fábio Miranda
The Part 2 of this series ended
with two docker commands that start an app and a database container. Long and
verbose, they aren’t practical to use regularly during the work. Worse: more
commands are needed to be able to run database migrations, install new gem/yarn
dependencies, debug code, and so on. After moving the complexity from the
command line to the docker-compose.yml
configuration file, this article
demonstrates a possible Docker-on-Rails style workflow.
Composing the Postgres settings
Starting a container can be as easy as docker-compose --rm up pg
, by moving
all the command line settings to the docker-compose.yml
file:
% docker run --rm \
-v databases:/var/lib/postgresql/data \
-e POSTGRES_HOST_AUTH_METHOD=trust \
--network demo-network \
--network-alias pg \
-dp 5432:5432 postgres
is translated to the following settings. It’s not necessary to explicitly declare a network, because Docker Composer implicitly creates it, as well the name of the service is implicitly set to its network alias.
version: "3.8"
volumes:
databases:
services:
pg:
image: postgres
ports:
- 5432:5432
volumes:
- databases:/var/lib/postgresql/data
environment:
POSTGRES_HOST_AUTH_METHOD: trust
Let’s test. The demo_default
network is automatically created, as well as
the demo_pg_1
container. The Postgres directory already exists because the
databases
volume was already previously mounted in this series’
Part 2.
% docker-compose up pg
Creating network "demo_default" with the default driver
Creating demo_pg_1 ... done
Attaching to demo_pg_1
pg_1 |
pg_1 | PostgreSQL Database directory appears to contain a database; Skipping initialization
pg_1 |
pg_1 | 2021-11-05 18:20:13.060 UTC [1] LOG: starting PostgreSQL 14.0 (Debian 14.0-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
pg_1 | 2021-11-05 18:20:13.061 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
pg_1 | 2021-11-05 18:20:13.061 UTC [1] LOG: listening on IPv6 address "::", port 5432
pg_1 | 2021-11-05 18:20:13.066 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
Composing the App Settings
Let’s see how to configure this command-line into docker-compose.yml
.
% docker run --rm \
--network demo-network \
-v "$(pwd):/src" \
-v "gems:/usr/local/bundle" \
-p 3000:3000 \
-e POSTGRES_HOST=pg \
-e POSTGRES_USER=postgres \
demo rails s -b 0.0.0.0
Again, the network can be omitted because of the composer-created demo_default
network. The bind volume declaration is simpler .:/src
because it doesn’t have
to invoke the command line’s pwd
utility. The gems
named volume is declared,
as well the ports mapping and the Postgres env vars.
The build
block is configured in a way that if something changes on the
Dockerfile
a new image will be built the next time docker-compose initializes.
Additionally, the depends_on
declaration tells Docker to init the pg container
before the app container.
volumes:
databases:
gems:
services:
pg:
...
app:
build:
context: .
dockerfile: Dockerfile
command: /bin/bash -c "bundle && rails db:create db:migrate && rm -f tmp/pids/server.pid && rails s -b 0.0.0.0"
ports:
- 3000:3000
volumes:
- ./:/src
- gems:/usr/local/bundle
environment:
POSTGRES_HOST: pg
POSTGRES_USER: postgres
depends_on:
- pg
Wrapping up
No more verbose commands to remember. And if using the Control + R
history
utility, starting the App and the Postgres containers is as simple as
Control + R up
and shutting down is Control + R down
.
# History command: Control + R up
% docker-compose up -d
demo_pg_1 is up-to-date
Starting demo_app_1 ... done
# History command: Control + R down
% docker-compose down
Stopping demo_app_1 ... done
Stopping demo_pg_1 ... done
Removing demo_app_1 ... done
Removing demo_pg_1 ... done
Removing network demo_default
This is just an initial demonstration of how convenient and powerful Docker Compose can be to set up everything. Considering the app will evolve and need more services like Redis, Sidekiq, ElasticSearch, etc, and it’s easy to see how it can help on improving productivity and create standardized working environments.
Working on containers
Logs
# History command: Control + R logs
% docker-compose logs -f
% docker-compose logs -f app # app logs only
% docker-compose logs -f pg # postgres logs only
Rails commands
The --rm
flag is useful to automatically remove the disposable
command container from the Docker Desktop list.
% docker-compose run --rm app rails c
% docker-compose run --rm app rails db:reset
% docker-compose run --rm app rails db:migrate
% docker-compose run --rm app rails test
It’s smart to add an alias for docker-compose run --rm app
into the
.bashrc
or .zshrc
files:
alias drails="docker-compose run --rm app rails"
Funny, don’t you think?
% drails c
% drails db:reset
% drails db:migrate
% drails test
Debugging
In the container, an annoying message gets printed on the logs, and it tells us the web console won’t be available to debug exceptions in the development mode:
Cannot render console from 172.26.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
It can be fixed by adding this piece of code to config/environments/development.rb
:
Rails.application.configure do
config.web_console.permissions = '172.0.0.0/8'
...
Finally, if a byebug
instruction is added to a Ruby code, for example on the
Home#index
action, the app will not stop on the breakpoint although Docker
prints the debugging info into the logs:
In order to make it work, it’s necessary to add 2 settings to the app config:
stdin_open: true
tty: true
After restarting the containers and refreshing the page the breakpoint will work, and the last thing to do is attaching the current terminal to the app container stdin. Terminating the debugging session can be done using the sequence Control + P,Q (Control + D would terminate the container execution).
% docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1c7b61d8c275 demo_app "/bin/bash -c 'bundl…" 2 minutes ago Up 2 minutes 0.0.0.0:3000->3000/tcp demo_app_1
9974f03b8b06 postgres "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 0.0.0.0:5432->5432/tcp demo_pg_1
% docker attach 1c7b61d8c275
(byebug)
Conclusion
I’m liking to learn Docker and I know what I learned and demonstrated on these 2 blog posts is just the beginning. I expect to post more articles while I keep learning this fascinating tool. The next articles will be focused on how I used this environment to develop a Hotwire application.