EQVPS

How to deploy a Docker Compose stack on a VPS

Deploy a multi-container stack on a VPS with Docker Compose: write docker-compose.yml, keep secrets in .env, bring it up with restart-on-reboot, and publish only the ports you mean to. Copy-paste example with a web app, database and reverse proxy.

Docker Compose describes a multi-container stack in one file and runs it with one command. This is the deploy how-to; first install Docker if you haven't. For an AI-agent-specific stack (agent + vector DB + Redis), see docker-compose for AI agents.

1. A stack: app + database + reverse proxy

# /opt/stack/docker-compose.yml
services:
  app:
    image: your/app:latest
    restart: unless-stopped
    env_file: [.env]
    depends_on: [db]
    # no ports: — reached only through the proxy below

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes: ["pg:/var/lib/postgresql/data"]   # internal only

  caddy:                                        # HTTPS automatically
    image: caddy:2
    restart: unless-stopped
    ports: ["80:80", "443:443"]                 # the only public entrypoint
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data

volumes: { pg: {}, caddy_data: {} }

Secrets live in .env, never the compose file:

# /opt/stack/.env  (chmod 600, git-ignored)
DB_PASSWORD=a-long-random-string

A minimal Caddyfile proxies your domain to the app and fetches HTTPS on its own:

app.yourdomain.com {
  reverse_proxy app:8080
}

2. Bring it up

cd /opt/stack
docker compose up -d
docker compose ps
docker compose logs -f app

restart: unless-stopped on every service + Docker enabled on boot = the stack returns after a reboot on its own.

3. Everyday commands

docker compose pull && docker compose up -d   # update images
docker compose down                            # stop the stack
docker compose restart app                     # restart one service

The part people get wrong: exposed ports

Docker publishes ports through iptables, so a container with ports: ["5432:5432"] is reachable from the internet even behind a UFW firewall you thought was closed. Publish only the public entrypoint (the proxy on 80/443); keep databases and caches on the compose network or bound to 127.0.0.1. Anything serving web traffic needs a dedicated-IP plan; pair with a UFW firewall.

For a whole stack behind one IP, see self-host multiple services. Root in about a minute, NVMe, no KYC, pay in crypto.

FAQ

How do I deploy a Docker Compose stack on a VPS?

Install Docker with the Compose plugin, write a docker-compose.yml describing each service, keep secrets in a git-ignored .env, then docker compose up -d. Set restart: unless-stopped on each service and enable Docker on boot so the stack survives reboots. The example below runs a web app, Postgres and a Caddy reverse proxy.

Where do secrets go — in the compose file?

No. Put them in a .env file (chmod 600, git-ignored) and reference them as ${VAR} in compose, or use Docker secrets. Never commit passwords or API keys into docker-compose.yml — a leaked file with live credentials is the classic mistake.

Will the stack come back after a reboot?

Only if you tell it to. Set restart: unless-stopped (or always) on each service and enable Docker on boot (systemctl enable docker). Then docker compose up -d survives reboots and restarts crashed containers.

Which ports get exposed?

Only what you publish with ports:. Bind internal services (databases, caches) to the compose network or 127.0.0.1 and publish just the public entrypoint — usually a reverse proxy on 80/443. Docker publishes through iptables, so an accidental ports: line is reachable from the internet even behind UFW.

Do you ask for ID or a card?

No. Email to sign up, pay in USDC or USDT — no documents, no card. Root in about a minute.

Comments

No comments yet. Be the first.

Leave a comment

Comments are moderated before they appear.