3 min read

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.

💡
Creating this new network is going to open up any ports that you map to the outside world. You’ll need to add firewall rules to your server in order to protect access to these ports.

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.

  1. Use kamal accessory reboot db to reboot the accessory, this will result in a short downtime while Kamal stops and starts the container.
  2. 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 run docker network inspect hey and you should see your db accessory container in the list of Containers.

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.

💡
Need help migrating to Kamal? Let's hop on an intro call to get you going.