Skip to content

use case

A Docker host you fully control

A full VM means a real kernel, real cgroups and no surprises with overlay filesystems. Install Docker, write a Compose file, ship.

Why a VM, not a container platform

Container platforms are convenient until you need a privileged container, a custom network driver, a sidecar that reads the host's journal, or a volume larger than the platform likes. On a FastNode VM the kernel is yours. Docker, Podman, containerd, k3s and Nomad all behave exactly as their docs describe.

NVMe-backed storage helps more than people expect. Image pulls, layer extraction and database volumes are all write-heavy, and overlay filesystems amplify small random I/O. Fast disks make docker compose up on a fresh host noticeably quicker.

Install Docker Engine

On Debian 12 or 13:

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/debian $(. /etc/os-release; echo $VERSION_CODENAME) stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

On Ubuntu swap debian for ubuntu in both URLs. On AlmaLinux or Rocky Linux 9:

sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
sudo dnf -y install docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker

Set sane daemon defaults

The default JSON log driver never rotates. A chatty container will fill a 20 GB disk in days. Fix it once in /etc/docker/daemon.json:

{
  "log-driver": "local",
  "log-opts": { "max-size": "20m", "max-file": "5" },
  "live-restore": true
}

Then sudo systemctl restart docker. The live-restore flag keeps containers running while the daemon restarts, which makes Docker upgrades far less dramatic.

The firewall trap

This catches nearly everyone once. When you publish a port with -p 5432:5432, Docker writes iptables rules that accept the traffic before UFW or firewalld sees it. Your host firewall says the port is closed; the internet disagrees.

Two fixes, use both:

  • Publish internal services on loopback only: 127.0.0.1:5432:5432. Only the reverse proxy should listen publicly.
  • Set rules in the FastNode portal firewall. It filters traffic before it reaches the VM, so Docker's iptables changes cannot override it.

A Compose layout that ages well

services:
  proxy:
    image: caddy:2
    ports: ["80:80", "443:443"]
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
    restart: unless-stopped
  app:
    image: ghcr.io/you/app:1.8.2
    env_file: .env
    expose: ["8080"]
    restart: unless-stopped
    depends_on: [db]
  db:
    image: postgres:16
    volumes: [pgdata:/var/lib/postgresql/data]
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/pg
    secrets: [pg]
    restart: unless-stopped
volumes: { caddy_data: {}, pgdata: {} }
secrets:
  pg: { file: ./secrets/pg.txt }

Pin image tags. latest turns a reboot into an unplanned upgrade. Keep the Compose file and Caddyfile in Git and deploy with git pull && docker compose up -d.

Updates without downtime surprises

Pull new images first, then recreate only what changed: docker compose pull && docker compose up -d. Compose leaves untouched services running. For the engine itself, read the release notes, snapshot, then upgrade the packages; with live-restore enabled your containers keep serving while the daemon restarts underneath them.

Housekeeping

# reclaim space from old images and build cache
docker image prune -af --filter "until=336h"
docker builder prune -f
# see what is actually using disk
docker system df -v

Schedule the prune weekly. Back up named volumes separately from snapshots: a pg_dump to another location protects you from a mistake that a snapshot would faithfully preserve.

Which plan

StackPlan
Proxy plus one small appSpark
App, Postgres, Redis, proxyBoost or Turbo
Ten-plus containers, search engine, metricsNitro (12 GB RAM)
Single-node k3s with real workloadsWarp or Hyper

Elasticsearch, OpenSearch and JVM services set their heap aggressively; cap it explicitly or they will compete with everything else for RAM. Prices are listed in US dollars, which is the currency we invoice in at the moment.

Questions

Does Docker work on every FastNode image?

Docker Engine runs on all the Linux images we offer: Ubuntu 22.04 and 24.04, Debian 12 and 13, AlmaLinux 9 and Rocky Linux 9. Use the official Docker repository for your distribution rather than the distro's older package.

Why can strangers reach a container port I thought was firewalled?

Docker inserts its own iptables rules ahead of UFW, so a published port bypasses host firewall rules. Bind published ports to 127.0.0.1 and put a reverse proxy in front, or rely on the portal firewall, which sits outside the VM.

Can I run Kubernetes instead?

Yes. A single-node k3s cluster fits comfortably on Turbo or larger. For multi-node clusters, run several VMs and join them over the public network with encrypted traffic, since private networking is not offered.

How do I recover if an upgrade breaks my stack?

Take a snapshot from the portal before upgrading Docker or changing your Compose file. Restoring it returns the whole VM, volumes included, to the moment you took it.