DEV Community

Cover image for Deploy a web app on VPS with Docker, Kamal
hungle00
hungle00

Posted on • Edited on

Deploy a web app on VPS with Docker, Kamal

Introduction

When the Docker container came, it made building web applications easier for developers in both development and deployment. Before, I used Docker for development, but now I have decided to learn how to use it for deployment.
When you search the Internet, you may encounter many complex terms for newbies, such as CI/CD, K8s, and docker swarm. In this post, I'll focus on a basic understanding of the deployment workflow and then, deploy a simple Go web app on my Linode VPS.

In my opinion, the basic workflow behind Docker deployment can be divided into 4 steps:

  1. Build an image from your application code.
  2. Push the image you built in the previous step to the Docker service ( docker hub, Amazon ECR, ...).
  3. Write a file (regularly .yml) that defines each needed service.
  4. Run a file in step 3 to pull images, setup and run each service on your VPS.

Docker deployment diagram

Sample Golang web app

For saving time, I'll use the existing repo, at the beginning I decided to use my old Rails app. But after going around on the Internet, and seeing the exciting repo that was written in Go, I'll use it for demo deployment.
Here's my Git repo for this demo: https://github.com/hungle00/hacker-live
You can see a Dockerfile to build an image for this app in this repo.
Dockerfile for hacker-live

Use docker-compose

I will use Treafik for the proxy server, which will act as the middle-man between the outside web and your VPS. The advantage of Treafik compared to other proxy servers is that it can automatically detect our running containers.
Here's the docker-compose.yml file that defines 2 services: Treafik proxy and our Go app.

version: '3'

services:
  reverse-proxy:
    # The official v3 Traefik docker image
    image: traefik:v3.3
    # Enables the web UI and tells Traefik to listen to docker
    command: --api.insecure=true --providers.docker
    ports:
      # The HTTP port
      - "80:80"
      # The Web UI (enabled by --api.insecure=true)
      - "8080:8080"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock
  hacker-live:
    image: jamesjoyce/hacker-live
    container_name: hacker-live
    labels:
      - "traefik.http.routers.hacker-live.rule=Host(`Your IP/domain`)"
Enter fullscreen mode Exit fullscreen mode

You just ssh to your server, copy docker-compose.yml file and then run:

docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

This approach is not practical because every time you change code, you must rebuild this image, then ssh to the server, and edit the docker-compose file to redefine the services.
In reality, we need the tool for the automatic setup of these steps. In the next part, I'll introduce Kamal for deployment.

Use Kamal

Kamal is the tool developed by the Rails team for deploying and managing your web app in production with Docker.
Simply speaking, Kamal is mostly a bunch of scripts to automate Docker deploys. When you run kamal init, it will generate some files that need for deployment, the most important file is config/deploy.yml.
For example, below is the config/deploy.yml file I use

# Name of your application. Used to uniquely configure containers.
service: hacker-live

# Name of the container image.
image: jamesjoyce/hacker-live

# Deploy to these servers.
servers:
  web:
    - 192.168.0.1  # change this with your IP
  # job:
  #   hosts:
  #     - 192.168.0.1
  #   cmd: bin/jobs

proxy:
  ssl: true
  host: app.example.com
  # Proxy connects to your container on port 80 by default.
  # app_port: 3000

# Credentials for your image host.
registry:
  # Specify the registry server, if you're not using Docker Hub
  # server: registry.digitalocean.com / ghcr.io / ...
  username: jamesjoyce

  # Always use an access token rather than real password (pulled from .kamal/secrets).
  password:
    - KAMAL_REGISTRY_PASSWORD

# Configure builder setup.
builder:
  arch: amd64
  # Pass in additional build args needed for your Dockerfile.
  # args:
  #   RUBY_VERSION: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>
Enter fullscreen mode Exit fullscreen mode

As you can see, I didn't define service for Treakfik. This is because Kamal automatically uses their Kamal proxy for the proxy server.
After config, then you run:

kamal deploy
# or kamal setup
Enter fullscreen mode Exit fullscreen mode

It will SSH into each server, prepare it with any necessary dependencies, and build and push the latest images to the docker registry.

You can read more about Kamal at the official docs. There are many concepts that I didn't introduce in this post, for example: manage accessories (db/redis/search), manage logging, ...

What’s next

In this post, I just introduce how to deploy a very basic web app on VPS using Docker. In reality, you may need more to deploy and manage your web app in production, for example:

  • Some services like database, job workers, Redis, ...
  • Monitoring your server (memory usage, CPU, disk, etc.) ...
  • Setup CI/CD

Top comments (0)