Docker and Docker Compose

The parts of Docker that matter once something is actually running.

9guides Feed

Docker is straightforward until something is running in front of users. At that point the interesting questions are networking, storage and lifecycle, and none of them are covered by the getting-started guide.

Reach for Compose on a single host for almost any self-hosted application. It is declarative, the file lives in git, and it handles multi-service dependencies without any orchestrator to operate. Reach for something larger only when you genuinely need scheduling across machines.

The networking behaviour that surprises people is port publishing. Docker manipulates iptables directly, and its rules run before the ones UFW manages. A container published with -p 5432:5432 is reachable from the internet even when the firewall shows a default-deny policy. The fix is to bind published ports to loopback and add an explicit rule to the DOCKER-USER chain.

Storage catches people twice. Named volumes are invisible until you need to back one up, and a tar of a live database directory restores into a corrupt cluster. Databases must be dumped by their own tooling; file volumes can be copied.

Lifecycle is the third. Without a health check, docker compose up reports success the moment a container is created, which means a crash-looping deploy goes green. With one, --wait gives you a deploy step that fails when it should.

Everything here assumes Compose v2 and a recent Docker Engine, states versions explicitly, and verifies the result rather than asserting it.

Start here

How to Deploy n8n on an Ubuntu VPS with Docker

A production n8n install on Ubuntu 24.04 using Docker Compose, PostgreSQL and Caddy for automatic TLS — with the encryption key, webhook URL and backup steps most quickstarts leave out.

Intermediate · 5 min read

Beginner to advanced

A reading order that builds Docker knowledge in the sequence it is actually needed.

  1. Start How to Deploy n8n on an Ubuntu VPS with Docker 5 min
  2. Build How to Back Up Docker Volumes Automatically 4 min
  3. Operate Zero-Downtime Deploys in Dokploy with Docker Health Checks 4 min
  • intermediate

    How to Secure a VPS That Runs Docker Containers

    SSH hardening, a default-deny firewall, and the fix for the problem that catches almost everyone — Docker publishing container ports straight past UFW into the public internet.

    4 min
  • intermediate

    How to Deploy n8n on an Ubuntu VPS with Docker

    A production n8n install on Ubuntu 24.04 using Docker Compose, PostgreSQL and Caddy for automatic TLS — with the encryption key, webhook URL and backup steps most quickstarts leave out.

    5 min

Common problems

Failure modes that come up repeatedly with Docker, and where each one is solved.

Published ports bypass the host firewall
Docker writes DNAT rules that are evaluated before UFW's chains. Bind to 127.0.0.1, and put an explicit DROP in the DOCKER-USER chain.
Container logs: too many open files
A file descriptor limit, inherited from the daemon rather than your shell. Raise it per service with `ulimits.nofile` or globally in daemon.json.
A deploy drops requests for a few seconds
The container has no health check, so the orchestrator shifts traffic to a process that is still starting.
A volume was lost and there is no usable backup
Tarring a live database directory does not produce a restorable copy. Dump databases with their own tools, snapshot file volumes separately.

Latest Docker guides

Browse the archive
  • intermediate

    How to Back Up Docker Volumes Automatically

    A nightly backup for a self-hosted Docker host: database dumps taken with the right tools, file volumes snapshotted with restic, a systemd timer to run it, and a restore test that proves it works.

    4 min AI-assisted
  • intermediate

    How to Build a GitHub Actions Deployment Pipeline

    Test, build a container image, push it to GHCR and deploy it to a VPS over SSH — with the concurrency guard, environment approval and token scoping that keep the pipeline from becoming the weak point.

    4 min AI-assisted
  • intermediate

    How to Run Meilisearch in Production with Docker

    A Meilisearch deployment that survives a real workload — master key handling, scoped API keys, the index settings you must set before loading data, snapshots, and what its memory usage actually means.

    4 min AI-assisted
  • advanced

    How to Run Cloudflare Tunnel in Front of Traefik

    Expose Docker services through Cloudflare with no inbound ports open — cloudflared plus Traefik, the forwarded-header setting that trips people up, and how to verify the firewall is closed.

    4 min
  • intermediate

    Dokploy vs Coolify: Choosing a Self-Hosting Platform

    Both put a Heroku-style deploy UI on your own VPS, but on different container primitives. What that means for zero-downtime deploys, multi-server growth and the day the control plane breaks.

    6 min AI-assisted

Recently updated

  • intermediate

    Dokploy vs Coolify: Choosing a Self-Hosting Platform

    Both put a Heroku-style deploy UI on your own VPS, but on different container primitives. What that means for zero-downtime deploys, multi-server growth and the day the control plane breaks.

    6 min AI-assisted
  • intermediate

    How to Secure a VPS That Runs Docker Containers

    SSH hardening, a default-deny firewall, and the fix for the problem that catches almost everyone — Docker publishing container ports straight past UFW into the public internet.

    4 min
  • intermediate

    How to Back Up Docker Volumes Automatically

    A nightly backup for a self-hosted Docker host: database dumps taken with the right tools, file volumes snapshotted with restic, a systemd timer to run it, and a restore test that proves it works.

    4 min AI-assisted

Frequently asked questions

Named volumes or bind mounts?

Named volumes for data the container owns — databases, application state. Bind mounts for configuration you edit on the host. Bind mounts also bring file ownership problems, because the container's user ID rarely matches yours.

Should I publish container ports at all?

Only for what the outside world must reach, and then bind explicitly to 127.0.0.1 unless it genuinely needs to be public. Containers on the same Compose network reach each other by service name with no published port.

Does docker compose up wait for containers to be ready?

Not by default — it returns once containers are created. Add `--wait`, which blocks until health checks pass, and make sure the services actually define a healthcheck or there is nothing to wait for.

Sources

  1. Docker Engine documentation Docker Primary source
  2. Compose file reference Docker Primary source
  3. Docker volumes Docker Primary source