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 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
demo_pg_1 container. The Postgres directory already exists because the
databases volume was already previously mounted in this series’
% 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  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  LOG: listening on IPv4 address "0.0.0.0", port 5432 pg_1 | 2021-11-05 18:20:13.061 UTC  LOG: listening on IPv6 address "::", port 5432 pg_1 | 2021-11-05 18:20:13.066 UTC  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 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
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.
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.
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
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
# 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
--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
alias drails="docker-compose run --rm app rails"
Funny, don’t you think?
% drails c % drails db:reset % drails db:migrate % drails test
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
Rails.application.configure do config.web_console.permissions = '18.104.22.168/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)
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.