We can deploy applications to VPS / Cloud Instances to avail it in the public internet. When deploying for production we need some standard practices to ensure application is easy to manage.

Some of the feature to consider are:

  • Auto TLS
  • Easy image building and deployment
  • Rolling updates
  • Rollback when needed

Preparing VPS for deployment

Setup a brand new VPS with SSH and Necessary settings example

  • Install Docker on VPS
    • ssh into VPS using ssh vps1.example.com
    • install docker
curl -fsSL https://get.docker.com | sudo sh

Setting up Docker Context

We can use docker context to configure docker client to connect to the VPS. We can create a context using:

docker context create vps1 --docker "host=ssh://vps1.example.com"

Note: This setup needs SSH keys and DNS entries.

Preparing docker compose with Traefik

I’d like to deploy Traefik as reverse proxy and auto TLS for my applications.

Along with traefik, Beszel is a great lightweight monitoring tool to view system metrics.

The docker compose file looks like this:

Note: generate password with htpasswd -nb admin password and replace the line - "traefik.http.middlewares.auth-middleware.basicauth.users=admin:password"

services:
  traefik:
    image: traefik:3
    labels:
      - "traefik.enable=true"
      # Dashboard router configuration
      - "traefik.http.routers.dashboard.rule=Host(`vps1.example.com`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
      # Basic auth middleware
      - "traefik.http.middlewares.auth-middleware.basicauth.users=admin:password" #
      - "traefik.http.routers.dashboard.middlewares=auth-middleware"
    command:
      - "--providers.docker"
      - "--providers.docker.exposedbydefault=false"
      #      TLS
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=adharsh.knullsoft@gmail.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      #      WebGateway
      - "--entrypoints.web.address=:80"
      - "--entryPoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      #     Dashboard configuration
      - "--api.dashboard=true"
      - "--api.insecure=false"
      #     Metrics
      - "--metrics.prometheus=true"
      - "--entryPoints.metrics.address=:8099"
      - "--metrics.prometheus.entryPoint=metrics"
      - "--metrics.prometheus.addEntryPointsLabels=true"
      - "--metrics.prometheus.addServicesLabels=true"
    ports:
      - mode: host
        protocol: tcp
        published: 80
        target: 80
      - mode: host
        protocol: tcp
        published: 443
        target: 443
    volumes:
      - letsencrypt:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      gateway:
    deploy:
      placement:
        constraints:
          - node.role == manager

  beszel:
    # default port is 8090
    image: henrygd/beszel:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.services.beszel.loadbalancer.server.port=8090"
      # https router configuration
      - "traefik.http.routers.beszel.entrypoints=websecure"
      - "traefik.http.routers.beszel.rule=Host(`beszel-vps1.example.com`)"
      - "traefik.http.routers.beszel.service=beszel"
      - "traefik.http.routers.beszel.tls.certresolver=letsencrypt"
    volumes:
      - beszel_data:/beszel_data
    networks:
      - gateway
    deploy:
      placement:
        constraints:
          - node.role == manager

volumes:
  beszel_data:
  letsencrypt:

networks:
  gateway:

Note: once beszel is deployed we can access it using https://beszel-vps1.example.com and continue setup for agent beszel docs

Deploy the stack

docker context use vps1

docker stack deploy -c docker-compose.yml vps1

Building images in VPS itself

Building image with docker context will allow images to be built and available in the VPS directly without Registry.

But It’s not recommended to build images on VPS as it will take more time and resources.

docker context use vps1

docker build -t myapp:latest .

Summary

With this setup a VPS is ready to accept more applications using docker stack.

new apps can use the following template:

services:
  nginx:
    # default port is 8090
    image: nginx:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.services.myapp.loadbalancer.server.port=8090"
      # https router configuration
      - "traefik.http.routers.myapp.entrypoints=websecure"
      - "traefik.http.routers.myapp.rule=Host(`app-vps1.example.com`)"
      - "traefik.http.routers.myapp.service=myapp"
      - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
    networks:
      - gateway

networks:
  gateway:
    extrenal: true # Connect to same network as traefik

This setup covers:

  • Auto TLS with Traefik
  • Easy image building and deployment ( docker build -t myapp:latest . )
  • Rolling updates ( docker stack deploy -c new_docker-compose.yml vps1 )
  • Rollback on failure ( docker service update --rollback service_name )

We could run all docker commands in the VPS remotely from our local machine using docker context use vps1

It’s a great stack to deploy small apps without complicated orchestration.