Single server Kamal deployments with a Docker network
Kamal doesn’t currently do anything with Docker networks but it’s easy to add one in to get docker compose-like networking in place. This is helpful so that you can connect to your db at service-db
and redis at service-redis
within your main application instead of dealing with IP addresses.
This single server deployment approach with Kamal really only comes in handy for very small apps or staging environments in my opinion. It’s too easy to isolate your services and roles by hosts with Kamal and you can more easily pinpoint and troubleshoot issues when they arise just by knowing the host it’s happening on.
Any user-defined bridge network with Docker provides the automatic DNS name based resolution that you’re familiar with, it’s just a matter of creating one. The default bridge doesn’t automatically enable this for you and it’s better to define your own network and then have the specific containers for your application join that network.
To get this going with Kamal you’ll first need to create the new Docker network on your server and name it after your service.
docker network create -d bridge hey
Now that you have your new network created the next step will depend on if you’ve already booted your accessory that you’d like to connect to. We’ll work on our db accessory for this.
Say we have a MySQL-based db accessory like so:
accessories:
db:
image: mysql:8.0
host: 123.456.789.0
port: 3306
env:
clear:
MYSQL_DATABASE: 'hey_production'
MYSQL_ROOT_HOST: '%'
secret:
- MYSQL_ROOT_PASSWORD
If we haven’t booted it yet, it’s just a matter of adding a network setting within the options for the accessory and then we can boot it with kamal accessory boot db
accessories:
db:
image: mysql:8.0
host: 123.456.789.0
port: 3306
env:
clear:
MYSQL_DATABASE: 'hey_production'
MYSQL_ROOT_HOST: '%'
secret:
- MYSQL_ROOT_PASSWORD
# These two lines
options:
network: hey
If you have booted your accessory, you have two options.
- Use
kamal accessory reboot db
to reboot the accessory, this will result in a short downtime while Kamal stops and starts the container. - Hop onto your server and just tell the running db container to join the network via Docker with
docker network connect hey hey-db
. Kamal names our accessories based off of the service name so#{service}-#{accessory}
. While you’re on the server go ahead and rundocker network inspect hey
and you should see your db accessory container in the list ofContainers
.
Now that you have one of your accessories connected to the bridge network you can update the configuration for your application to utilize hey-db
as the host for connecting to the database. I typically use a DATABASE_URL
in either Rails credentials or in my .env.production file.
database_url: mysql2://root:secretpw@hey-db:3306/hey_production
Once that’s in place you just need to add the same options to any roles that need to be on the same network to reach the database as well as traefik.
First, go ahead and add the network to traefik and reboot traefik so it joins the new network.
traefik:
options:
network: hey
Then we can go ahead and add the network to our web role.
servers:
web:
hosts:
- 123.456.789.0
options:
network: hey
With that in place you can now test out a deploy and you should start seeing a new option passed to the docker run
lines in the Kamal output that mentions --network hey
.
Once that deploy is complete, ensure that you can’t see the MySQL port open on your server now with a quick nmap port scan, you can also use telnet for this.
nmap -p 3306 123.456.789.0
You should see “filtered” within the output, if not go tighten up your firewall.
PORT STATE SERVICE
3306/tcp filtered mysql
If it’s open it’ll say open in the nmap output.
PORT STATE SERVICE
3306/tcp open mysql
From there you can repeat for redis and any other backing services that you need. Kamal might change how this functions in the future and it’s really meant for multi-host deployments but for small apps and staging environments, it’s pretty nice to be able to do this.