Skip to main content

Running Docker on your VPS

Install Docker, understand volumes and networking, and avoid the classic data-loss traps.

Docker packages an app and its dependencies so it runs the same everywhere. · source code · Compose

Install

bash
# official convenience script (review it first, as with any script)
curl -fsSL https://get.docker.com | sh
docker --version
docker compose version

The mental model

  • Image — a read-only template built from a Dockerfile.

  • Container — a running instance of an image. Disposable by design.

  • Volume — persistent storage that outlives containers. Your data lives here.

  • Network — an isolated virtual network containers attach to.

Compose is how you actually run things

A compose.yaml describes your services. Example:

yaml
services:
  app:
    image: myapp:latest
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"     # loopback only; put a reverse proxy in front
    volumes:
      - appdata:/data
    environment:
      - DATABASE_URL=postgresql://...
volumes:
  appdata:
bash
docker compose up -d          # start
docker compose logs -f app    # follow logs
docker compose pull && docker compose up -d   # update
docker compose down           # stop (volumes preserved)

The traps that eat data

  1. docker compose down -v deletes volumes. That flag removes your data. Never run it on anything you care about.

  2. docker system prune -a removes unused images; --volumes also removes volumes. Plain prune (images only) is usually safe; be careful with the volume flag.

  3. Data in the container's filesystem vanishes on recreation. Always mount a volume for anything persistent.

  4. Binding to 0.0.0.0. Map ports to 127.0.0.1 and proxy through Nginx/Caddy so a container never opens a public port by accident.

Networking

Containers on the same Compose network reach each other by service name — postgres, redis — not by IP. That is the whole point of the network abstraction.

Security

  • Do not run containers as root where avoidable (user: in compose).

  • Pin image tags (postgres:16.4) rather than :latest, so an update is a decision, not a surprise.

  • Rebuild on a schedule to pick up base-image security patches.

  • Container escape is real: keep Docker daemon access equal to root access in your threat model.

Disk hygiene

Docker accumulates images, build cache and logs:

bash
docker system df            # what is using space
docker image prune -f       # remove dangling images
docker builder prune -f     # clear build cache

Related reading