Using Kamal to host multiple Apps on a single server
Use the Kamal deployment tool to host multiple Applications on a single server instance
In this post I'll outline a way you can host multiple Ruby on Rails applications on a single server. This is one way to achieve that desired goal, there may be better ways.
I understand that many developers may not carry-over to the dark side of DevOps things. Comprehending all that Kamal is can be a daunting task. You should have some knowledge of Traefik and Docker.
In this example, we'll have 2 Rails Applications, luckily Rails 7.1.0.beta1 was recently released with all the new goodness. Both Applications in this example are the exact same, SQLite3, esbuild, vanilla out of the box configuration. I'm using Kamal version 1.0.0. The end goal of each was a green response at the route /up
.
Want to run your own Docker registry instance for Kamal? Check out my post about it.
Here's a bit of a visual of how things are setup on the single instance:
Kamal commands our server instance through the deploy.yml
file we create. If this instance is located on the cloud, you'll have the SSH key to login local to your machine, Kamal simply uses that to login as root and run commands for you.
Kamals kamal server
command will bootstrap the server with what is necessary, and at the base of it all is: docker and curl, the Traefik container being the reverse proxy we need to route the traffic in to each container.
This setup is a bit odd in that the first project you'll run the kamal init
command in will have some of the traefik configuration we need in it for SSL. If you're hosting a bunch of small projects and don't care if there is overlap into another project then you can continue.
After you've setup the server instance with docker you can login to it and create a directory /letsencrypt/acme.json
and file with the contents {}
, you can then chmod it 600.
Going to use Hetzner Cloud? Get €20 in credits when you use my link to sign up.
Site 1 Configuration
# deploy.yml
service: site1
image: my-registry/site1
# Deploy to these servers.
servers:
web:
hosts:
- yourip
options:
"add-host": host.docker.internal:host-gateway
labels:
traefik.http.routers.rails_recipes.entrypoints: websecure
traefik.http.routers.rails_recipes.rule: Host(`rails.recipes`)
traefik.http.routers.rails_recipes.tls.certresolver: letsencrypt
# Credentials for your image host.
registry:
# Specify the registry server, if you're not using Docker Hub
server: registry.digitalocean.com
username: deploy
# Always use an access token rather than real password when possible.
password:
- KAMAL_REGISTRY_PASSWORD
traefik:
options:
publish:
- "443:443"
volume:
- "/letsencrypt/acme.json:/letsencrypt/acme.json"
args:
entryPoints.web.address: ":80"
entryPoints.websecure.address: ":443"
entryPoints.web.http.redirections.entryPoint.to: websecure
entryPoints.web.http.redirections.entryPoint.scheme: https
entryPoints.web.http.redirections.entrypoint.permanent: true
certificatesResolvers.letsencrypt.acme.email: "you@youremail"
certificatesResolvers.letsencrypt.acme.storage: "/letsencrypt/acme.json"
certificatesResolvers.letsencrypt.acme.httpchallenge: true
certificatesResolvers.letsencrypt.acme.httpchallenge.entrypoint: web
After you have this in your deploy.yml
, save it. Be sure your env variables are up to date with kamal env push
. You'll notice the naming conventions Kamal uses for your services. This helps in the flexibility of being able to deploy multiple applications.
Run kamal deploy
to push this application up to the server.
If you haven't configured traefik yet, you'll what to likely run kamal traefik restart
or kamal traefik reboot
if its not picking up your changes. Give LetsEncrypt a little time to catch up with the certificate.
Site 2 Configuration
# deploy.yml
service: site2
image: my-registry/site2
servers:
web:
hosts:
- yourip
options:
"add-host": host.docker.internal:host-gateway
labels:
traefik.http.routers.site2-web.entrypoints: websecure
traefik.http.routers.site2-web.rule: Host(`changelog.lol`)
traefik.http.routers.site2-web.tls.certresolver: letsencrypt
registry:
# Specify the registry server, if you're not using Docker Hub
server: registry.digitalocean.com
username: deploy
# Always use an access token rather than real password when possible.
password:
- KAMAL_REGISTRY_PASSWORD
The same goes for this site, be sure you have your env variables up to date and push them. They'll end up in .kamal/env/roles
directory on your server instance. You can SSH and check them out there, too.
Now all you need to do is run the kamal deploy
command from your other application directory and Kamal will deploy this app for you and start the container. Since we already have Traefik running it will get notified of the changes via the labels we're pushing. You can also watch this with the docker logs command from the server.
I hope this helps you with general concepts or debugging getting your application deployed.
This is dedicated to someone who kept asking me how to do this.
Well, you've made it this far. If you happen to be a developer, check out my in-app feedback tool that I'm hacking on.