Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ismarsantos/bd4c538c5f6923017632d8bfd8dc3a36 to your computer and use it in GitHub Desktop.
Save ismarsantos/bd4c538c5f6923017632d8bfd8dc3a36 to your computer and use it in GitHub Desktop.
Deploy a web app on a dedicated server with Kamal

Warning

This gist is still a draft. At the moment it is not functional: see basecamp/kamal#257

Motivation

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.

Setup your dedicated server

Setup your domain names

  • Add A/AAAA record in your DNS records for server.mydomain.com, registry.mydomain.com and myapp1.mydomain.com to the IP of your dedicated server

Setup a private container registry

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.

Install SSL certificates for the registry

  • 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;

Open postgresql access to the docker containers

  • Add host all all 172.17.0.0/24 scram-sha-256 to /etc/postgresql/16/main/pg_hba.conf

Open redis access to the docker containers

  • Edit /etc/redis/redis.conf to set bind 0.0.0.0 and protected-mode no
  • Allow access for docker containers sudo ufw allow proto tcp from 172.17.0.0/24 to any port 6379

Create a database

  • sudo -u postgres createuser myapp1 --createdb --pwprompt --encrypted
  • Update database.yml, add DB_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 } %>

Install Kamal in your web app

  • 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 choose 7001.
  • Add host_port: 7001 to traefik: in deploy.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.

Create SSL certificates

  • Run sudo certbot certonly --nginx -d myapp1.mydomain.com
  • Edit production.rb in your app to set config.assume_ssl = true

Install reverse proxy with nginx

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

Visit website

  • Open https://app1.mydomain.com in your browser

Run console (for Rails)

  • kamal app exec -i 'bin/rails console'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment