This gist is still a draft. At the moment it is not functional: see basecamp/kamal#257
Kamal was designed with 1 service = 1 droplet/VPS in mind.
But I'm cheap and I want to be able to deploy multiple demo/poc apps apps on my $20/month dedicated server.
What the hell, I'll even host my private container registry on it.
- https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-22-04
- https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-22-04
- https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-22-04
- https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-22-04
- https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-redis-on-ubuntu-22-04
- https://www.digitalocean.com/community/tutorials/how-to-set-up-minio-object-storage-server-in-standalone-mode-on-ubuntu-20-04
- Add A/AAAA record in your DNS records for
server.mydomain.com
,registry.mydomain.com
andmyapp1.mydomain.com
to the IP of your dedicated server
Note 1: This tutorial says you need a host server and a client server. In our case, we will use only one server to be both client and server.
Note 2: The port 5000
is already used by datadog-agent by default, so I prefer using 7000:5000
instead.
- Run
sudo certbot certonly --nginx -d registry.mydomain.com
- Add to
/etc/nginx/sites-enabled/registry
:listen 443 ssl; listen [::]:443 ssl; ssl_certificate /etc/letsencrypt/live/registry.domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/registry.domain.com/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5;
- Add
host all all 172.17.0.0/24 scram-sha-256
to/etc/postgresql/16/main/pg_hba.conf
- Edit
/etc/redis/redis.conf
to setbind 0.0.0.0
andprotected-mode no
- Allow access for docker containers
sudo ufw allow proto tcp from 172.17.0.0/24 to any port 6379
sudo -u postgres createuser myapp1 --createdb --pwprompt --encrypted
- Update
database.yml
, addDB_HOST
,DB_USERNAME
,DB_PASSWORD
,DB_PORT
to your.env.erb
file.production: <<: *default host: <%= ENV["DB_HOST"] %> database: myapp1_production username: <%= ENV["DB_USERNAME"] %> password: <%= ENV["DB_PASSWORD"] %> port: <%= ENV.fetch("DB_PORT") { 5432 } %>
- Run Docker locally
- Follow: [https://kamal-deploy.org/docs/installation]
- Add database/redis credentials
- Run
bin/kamal env push
- Run
bin/kamal deploy
- Run
bin/kamal traefik reboot
after any change in the traefik configuration - Choose a unique port for your service, use
sudo lsof -i -P -n | grep LISTEN
to check which ports are already in use. For this guide I'll choose7001
. - Add
host_port: 7001
totraefik:
indeploy.yml
The deploy.yml
file should look like this:
# Name of your application. Used to uniquely configure containers.
service: my-app-1
# Name of the container image.
image: n-studio/my-app-1
# Deploy to these servers.
servers:
- server.mydomain.com
# Credentials for your image host.
registry:
# Specify the registry server, if you're not using Docker Hub
server: registry.mydomain.com
# Always use an access token rather than real password when possible.
username:
- KAMAL_REGISTRY_USERNAME
password:
- KAMAL_REGISTRY_PASSWORD
# Inject ENV variables into containers (secrets come from .env).
# Remember to run `kamal env push` after making changes!
env:
clear:
DB_HOST: server.mydomain.com
DB_PORT: 5432
REDIS_URL: redis://172.17.0.1:6379/1 # give a unique db number for each app
secret:
- RAILS_MASTER_KEY
- DB_USERNAME
- DB_PASSWORD
# Use a different ssh user than root
ssh:
user: ubuntu
# Configure custom arguments for Traefik
traefik:
name: traefik-my-app-1
host_port: 7001
args:
entrypoints.my-app-1-web.address: ':80'
labels:
traefik.http.routers.my-app-1-web.entrypoints: my-app-1-web
Note: This uses the branch n-studio/kamal:custom-traefik-name to allow a custom name for the traefik container.
- Run
sudo certbot certonly --nginx -d myapp1.mydomain.com
- Edit
production.rb
in your app to setconfig.assume_ssl = true
server {
server_name myapp1.mydomain.com;
listen 80;
listen [::]:80;
## redirect http to https ##
rewrite ^ https://$server_name$request_uri? permanent;
}
server {
server_name myapp1.mydomain.com;
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/letsencrypt/live/myapp1.mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp1.mydomain.com/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://localhost:7001;
proxy_set_header Host $http_host; # required for docker client's sake
proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host $host;
proxy_read_timeout 900;
}
}
- Run
sudo service nginx restart
- Open
https://app1.mydomain.com
in your browser
kamal app exec -i 'bin/rails console'