Lots of developers choose between dockerizing their development setup or leaving it as is. There is also a viable hybrid approach in combining Docker Compose with native processes.
I am usually in the camp of running things directly or creating Vagrant environments that closely resemble what I normally run. I also think a lot between introducing more layers than I need, so I usually run without Docker if I can.
Nevertheless, I realized that running a Docker Compose setup alongside your regular Puma and Sidekiq processes is actually a pretty nice sweet spot to be in. It’s what we use at Phrase.
Why, but why
The arguments for dockerizing the whole development environment are usually in terms of matching production. That means running the same versions of databases, utilities, and services. Having it formalized also means that every team member can immediatelly start working or return to a working setup.
I understand this argument a lot as it’s the reason I usually had a Vagrant environment around for my own projects. Even when I developed without a virtual machine, I would write a Vagrantfile to be able to run things in case of anything breaking. So I get it.
But it’s not the same with Docker. Dockerizing an entire development setup requires a bit different mindset in my opinion. And while leaving virtual machines behind sounds like an improvement, performance might still suffer.
This makes you think if dockerizing everything is worth it. Seems like full Docker setups are a minority for this reason.
Can we not go overboard and still enjoy some Docker, though? What’s an alternative?
The alternative is installating Ruby, Rails, and system utilities as usual while dockerizing the rest. This way we solve the annoying part of managing different databases at the cost of not solving the parity in system dependencies.
It’s not perfect, but it’s simple. It’s getting 80% of benefits for 20% of effort. The end result should be running bin/dev
and bin/rails test
as usual. Not a single command would have to run within a container.
Implementation
There are three steps to turn a regular setup to a hybrid Docker Compose one. We’ll write the docker-compose.yml
specification of our databases, update the ports in Rails configuration files, and finally include Docker Compose in our Procfile.dev
.
The Docker Compose file for a typical new Rails application with a relational database and Redis server might look like the following:
# docker-compose.yml
version: '3.7'
services:
postgres:
image: postgres:14.2
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 54320:5432
volumes:
- postgres:/var/lib/postgresql/data
redis:
image: redis:5.0.4
command: redis-server /etc/redis.conf
ports:
- 63790:6379
volumes:
- redis:/data
volumes:
redis:
postgres:
The first thing you might notice is that it’s very short and understandable. Two database services, each with a volume for data and ports we expose to the host. The PostgreSQL server is run with a default password while we can omit a password for Redis.
Remember that some other services or databases might require different development and test entries, but this is not necessary here as we can use the same servers for both environments (the database name will differ).
Running with this Compose setup is as easy as typing docker-compose up
and updating your Rails configuration.
If you have to run Docker with sudo
, add your user to the docker group first:
$ sudo gpasswd -a $USER docker
$ newgrp docker
And start Docker Compose:
$ docker-compose up
docker-compose up
should download the database images and start these two services for you.
Now that your databases are ready, update the Rails configuration:
# config/database.yml
development:
<<: *default
username: postgres
password: postgres
# 5432 for local, 54320 for Docker Compose
port: 54320
host: "0.0.0.0"
database: app_development
...
# config/cable.yml
development:
adapter: redis
# 6379 for local, 63790 for Docker Compose
url: redis://localhost:63790/1
...
At this point you should be able to run bin/rails s
, bin/rails test
and other usual commands against these new databases.
Finally, to put these things together, we’ll update Procfile.dev
:
$ cat Procfile.dev
web: bin/rails server -p 3000
css: yarn build:css --watch
live_reload: bin/guard
js: yarn build --watch
services: docker-compose up
Conclusion
If we now want to start Rails in development, all we have to do is to run bin/dev
as usual.
We haven’t solved everything with the new setup, but we gained a lot for very little effort. I think that’s the setup I’ll go with in my kit.
Get Test Driving Rails and make your tests faster and easier to maintain.