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/rails test as usual. Not a single command would have to run within a container.
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
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 test and other usual commands against these new databases.
Finally, to put these things together, we’ll update
$ 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
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.
← IT'S OUT NOW
I wrote a complete guide on web application deployment. Ruby with Puma, Python with Gunicorn, NGINX, PostgreSQL, Redis, networking, processes, systemd, backups, and all your usual suspects.