← Back to Blog

Run Docker on a VPS: Compose Stacks, Portainer and a Small Kubernetes Node

· by RS Computers

Docker Containers VPS

Docker changed how small teams run software: instead of installing PHP, Node, Postgres and Redis by hand and hoping they get along, you describe the whole stack in one file and start it with a single command. The same file runs on your laptop, on a colleague's machine and on the server. All it needs is a Linux box with root access and a real kernel, which is exactly what a KVM VPS is. This guide covers why Docker and a VPS fit together, why people run their containers at RS Computers, how to install Docker properly (including the firewall mistake almost everyone makes once), a ready-to-use Compose stack with automatic HTTPS and a web UI, backups, when to step up to a small Kubernetes node, and twelve things worth self-hosting on day one.

Why containers belong on a VPS

Why run Docker at RS Computers

Which plan for your stack?

PlanComfortable for
VPS Nano (1 vCPU, 1 GB, 20 GB NVMe)A reverse proxy plus two or three light containers: Uptime Kuma, Vaultwarden, a static site, a Telegram bot.
VPS Micro (2 vCPU, 2 GB, 40 GB NVMe)A typical self-hosting box: Caddy, Portainer, Nextcloud or Gitea, a Postgres database, n8n. Also enough for a single-node k3s with a few small services.
VPS Mini (4 vCPU, 4 GB, 80 GB NVMe)Ten or more containers, a CI runner, Mattermost or Mailcow, staging copies of client projects, or a more serious k3s node.
VDS Small to Large (4 to 16 vCPU, 8 to 32 GB, up to 960 GB NVMe, 10 Gb/s)Production stacks for several clients, databases with real traffic, media servers, object storage with MinIO, or a Kubernetes node that actually gets busy.

Getting a server

  1. On the VPS & VDS plans page, choose a location and click Get on a plan.
  2. Select Debian 12 or Ubuntu 24.04. Paste your SSH public key if you have one; otherwise a root password is generated for you.
  3. Pay. The server is built automatically, normally within a couple of minutes, and the IP addresses and root password arrive by email.
  4. For anything with a web interface, create DNS A records (and AAAA for IPv6) such as portainer.example.com and status.example.com pointing at the server.

Install Docker the right way

Step 1: Docker Engine and Compose

ssh root@YOUR_SERVER_IP
apt update && apt full-upgrade -y
curl -fsSL https://get.docker.com | sh
docker --version && docker compose version

Docker's convenience script installs the Engine, the Compose plugin and the Buildx plugin from Docker's own repository, so apt upgrade keeps them current.

Step 2: Log rotation and a non-root user

Container logs grow forever by default and have filled many a disk. Cap them once, globally:

cat > /etc/docker/daemon.json <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}
EOF
systemctl restart docker

Then create a user for daily work and add it to the docker group (which is root-equivalent, so treat that account like root):

adduser deploy
usermod -aG docker deploy

Step 3: The firewall trap

This is the mistake that bites almost everyone once. Docker writes its own iptables rules, so a port you publish with -p 5432:5432 or ports: ["5432:5432"] is reachable from the entire internet even if ufw says it is blocked. Databases, Redis instances and admin panels get exposed this way every day.

The clean solution is simple: never publish a port to 0.0.0.0 unless it is meant to be public. Bind internal services to localhost and put one reverse proxy in front of everything web-facing:

# exposed to the world: only the reverse proxy
ports:
  - "80:80"
  - "443:443"

# reachable only from this server (for a local tool or SSH tunnel)
ports:
  - "127.0.0.1:5432:5432"

# best of all for containers that only talk to each other: no ports at all,
# they reach each other by service name on the Compose network

With that habit, ufw can stay simple:

apt install -y ufw
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable

A starter stack: Caddy, Portainer and Uptime Kuma with automatic HTTPS

This gives you a reverse proxy that obtains certificates by itself, a web UI to manage containers from your phone, and a status page that watches your other services. Create /opt/stack/docker-compose.yml, replacing the two hostnames:

services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config

  portainer:
    image: portainer/portainer-ce:lts
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data

  uptime-kuma:
    image: louislam/uptime-kuma:1
    restart: unless-stopped
    volumes:
      - kuma_data:/app/data

volumes:
  caddy_data:
  caddy_config:
  portainer_data:
  kuma_data:

Notice that Portainer and Uptime Kuma publish no ports at all. Only Caddy is reachable from outside, and it forwards by hostname. Create /opt/stack/Caddyfile:

portainer.example.com {
    reverse_proxy portainer:9000
}

status.example.com {
    reverse_proxy uptime-kuma:3001
}

Start it and watch the certificates arrive:

cd /opt/stack && docker compose up -d
docker compose logs -f caddy

Open https://portainer.example.com within a few minutes of starting (Portainer locks its first-run setup page after a timeout; if that happens, docker compose restart portainer) and create the admin account. From now on you can add any application as a new Compose "stack" in Portainer's UI, and give it a hostname by adding three lines to the Caddyfile followed by docker compose restart caddy.

Everyday operations

Updating

cd /opt/stack && docker compose pull && docker compose up -d
docker image prune -f

Pin image tags for anything important (postgres:16, not postgres:latest) so an update never jumps a major version by accident. Tools such as Watchtower can automate pulls, but for databases a monthly manual update is the safer habit.

Backing up volumes

Your plan's free weekly backup covers the whole server. For application-level copies you control, archive a volume with a throwaway container:

docker run --rm -v stack_portainer_data:/data -v /root/backups:/backup alpine \
  tar czf /backup/portainer-$(date +%F).tgz -C /data .

For databases, dump rather than copy files: docker compose exec -T db pg_dump -U app app | gzip > /root/backups/db-$(date +%F).sql.gz. Put either in a cron job or an n8n workflow and ship the files to object storage off the server.

Limits and health

services:
  worker:
    image: ghcr.io/example/worker:1.4
    restart: unless-stopped
    mem_limit: 512m
    cpus: "0.5"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3

docker stats shows live usage per container, and Portainer graphs it. If you are regularly near the memory ceiling, that is the moment to upgrade the plan rather than to add swap.

When Compose is not enough: a small Kubernetes node with k3s

For most self-hosters and small businesses, Docker Compose is the right tool for years. Kubernetes starts to pay off when you need rolling deployments with health-based rollbacks, declarative configuration managed in Git, Helm charts for complex applications, or the same manifests in development and production. k3s is a lightweight, fully compliant Kubernetes distribution that fits on a single VPS and comes with the Traefik ingress controller and local storage already configured:

curl -sfL https://get.k3s.io | sh -
kubectl get nodes
kubectl create deployment hello --image=nginx:1.27 --port=80
kubectl expose deployment hello --port=80
kubectl get pods -o wide

VPS Micro is enough to learn and run a few small services; VPS Mini or a VDS is a comfortable single-node production host. When you later add a second server, it joins the same cluster with one command. One honest note: a single node is not high availability, so keep the weekly backups and treat it as a very capable single server.

Twelve things worth self-hosting with Docker

  1. Nextcloud: files, calendar and contacts for the team, with your data in your own data centre.
  2. Gitea or GitLab: private Git hosting, issues and CI runners, without per-seat pricing.
  3. n8n: automation and AI agents with unlimited executions (see our n8n guide).
  4. Vaultwarden: a Bitwarden-compatible password manager for the whole company on a tiny footprint.
  5. Uptime Kuma: monitoring and a public status page for your sites and services.
  6. Plausible or Umami: privacy-friendly website analytics with no cookie banner headaches.
  7. WordPress and other sites: each site in its own container with its own PHP version, all behind one Caddy or Traefik (or see the WordPress on a VPS guide for the control-panel route).
  8. Postgres, MySQL, Redis: databases for your applications, bound to localhost and backed up by cron.
  9. MinIO: S3-compatible object storage for backups and application uploads, on a VDS with lots of NVMe.
  10. Mattermost or Rocket.Chat: team chat you own, with a WireGuard tunnel (see our WireGuard guide) if you want it private.
  11. Game servers: Minecraft (itzg/minecraft-server), Valheim, Palworld and others ship as images and take minutes to start.
  12. CI runners and staging: a GitHub Actions or GitLab runner in a container, plus throwaway staging copies of client projects on their own subdomains.

Security checklist

Frequently asked questions

Does Docker work on all your plans?

Yes. Every plan is a KVM virtual machine with its own kernel, so Docker, Compose, Podman and k3s all run without special configuration, even on VPS Nano.

Can I run Docker on Windows Server?

Windows containers are possible on Windows Server (available on VPS Mini and all VDS plans), but the vast majority of images are Linux images. For Docker, order Debian or Ubuntu.

How many containers fit on VPS Micro?

It depends on the containers, not the count. Light services use 30 to 100 MB each, so a reverse proxy plus eight or ten small apps is realistic on 2 GB. A Java application or a large database changes the maths; watch docker stats.

Should I start with Compose or Kubernetes?

Compose. Learn it, run with it, and move to k3s when you feel a specific need such as rolling updates or Helm charts. Everything you learn about images, volumes and networks carries over.

Can RS Computers set this up for me?

Yes. Message us on Telegram or email info@rscomputers-ks.com with what you want to run and we will quote a setup, including the reverse proxy, HTTPS and backups.

Conclusion

A KVM VPS with NVMe storage and your own IP is the natural home for containers: Docker runs the way it was designed to, the whole stack is one file you can rebuild anywhere, and the apps the open-source world has built are a docker compose up away. Start with the Caddy and Portainer stack above, bind nothing to the public internet except the proxy, keep the weekly backups, and add services as you need them. Pick a plan, and your first container can be running before the coffee is cold.

← All articles

Chat on Telegram