Guide

Deploy a web app to a VPS with Docker Compose + HTTPS

Goal

Ship a small web app to your own VPS with Docker Compose, automatic HTTPS via Caddy, and a rollback path.

Prerequisites

  • A VPS with Docker + Docker Compose installed
  • A domain pointing at the VPS IP (A record)
  • App source code + a Dockerfile

Steps

  1. Get your app code onto the VPS: clone your repo into /srv/myapp (or copy the files), including your Dockerfile. Ensure ports 80/443 are open on the VPS (firewall + cloud security group).
    git clone <your-repo-url> /srv/myapp && cd /srv/myapp

    If you copy files by hand instead of cloning, create the folder first: mkdir -p /srv/myapp, then copy your code + Dockerfile into it.

  2. In /srv/myapp, create a compose.yml:
    services:
      web:
        build: .
        restart: unless-stopped
        env_file: .env
        expose: ["3000"]
      caddy:
        image: caddy:2
        restart: unless-stopped
        ports: ["80:80", "443:443"]
        volumes:
          - ./Caddyfile:/etc/caddy/Caddyfile
          - caddy_data:/data
    volumes:
      caddy_data:
  3. Caddyfile (replace app.example.com with your domain):
    app.example.com {
      reverse_proxy web:3000
    }
  4. Create .env on the VPS with real values — never commit it. Use placeholders in any message or log you share.
  5. Deploy:
    docker compose up -d --build
    docker compose logs -f --tail=100

Verify

  • https://your-domain (replace app.example.com with your domain) loads with a valid certificate (Caddy issues it automatically).
  • docker compose ps shows both services running.
  • Rollback: brief downtime while containers restart — run during low traffic if possible. Steps: check out the previous commit, then docker compose down && docker compose up -d --build.

Troubleshoot

  • HTTPS not issuing: DNS A record must point at the VPS IP; ports 80/443 must be open.
  • App crashes: check docker compose logs web and audit env with env-doctor.
  • Port conflict: something else is on 80/443 — stop it or move Caddy.

Last updated: 2026-10-16. Edit on GitHub · Report an issue

← Previous guide · Next guide →

Still stuck? Get deployment help →