← Back to blog

Docker on a VPS: How Containers Simplify Deployment

Maria Ilinca Bostan

Docker packages an application with its dependencies into an image that runs identically wherever the runtime exists. On a VPS that solves the two problems that make deployments painful: the machine drifting away from what you tested against, and rollbacks being a rebuild rather than a switch. A KVM VPS is a good host for it because you get a real kernel and full root — container runtimes need both.

Why KVM matters here

Containers share the host kernel. On a container-based virtualisation platform you are a container inside someone else's container, and nested runtimes, custom kernel modules, iptables manipulation and overlay filesystems all become fragile or forbidden. On KVM your VPS has its own kernel, so Docker behaves exactly as it does on bare metal. If you plan to run containers, this distinction is worth more than a slightly cheaper plan.

Installation

curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER   # log out and back in
docker run --rm hello-world

Adding your user to the docker group is equivalent to giving them root, because the daemon socket can mount the host filesystem. On a multi-user machine, prefer sudo docker or run rootless Docker.

A realistic compose file

services:
  app:
    build: .
    restart: unless-stopped
    environment:
      DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
    depends_on:
      db: { condition: service_healthy }
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:3000/healthz"]
      interval: 30s
      timeout: 3s
      retries: 3
    deploy:
      resources:
        limits: { memory: 1g, cpus: '1.0' }

  db:
    image: postgres:17-alpine
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      retries: 5

volumes:
  pgdata:

Three details do the heavy lifting. Memory limits stop one runaway container invoking the OOM killer and taking an unrelated process with it — on a 4 GB VPS this is not optional. Health checks with depends_on: condition stop the application starting before the database can answer, which is the most common cause of a stack that works locally and fails on first boot. Named volumes keep data outside the container so docker compose down is not a data-loss event.

Put a reverse proxy in front, not ports on the internet

Bind container ports to localhost and terminate TLS at Nginx:

ports:
  - "127.0.0.1:3000:3000"

A bare - "3000:3000" publishes to all interfaces — and Docker writes its own iptables rules, which can bypass a ufw rule you believed was protecting the port. This surprises people regularly. Verify with sudo ss -lntup after every compose change, and see the Nginx reverse proxy guide for the front end and the firewall guide for the interaction.

Keeping the disk from filling up

The most common Docker incident on a small VPS is a full disk. Images, build cache, stopped containers and dangling volumes accumulate quietly.

docker system df
docker system prune -af --volumes   # careful: removes unused volumes too

# cap log growth, which otherwise grows without bound
sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }
EOF
sudo systemctl restart docker

Set the log rotation before you need it. A chatty container will fill 40 GB with JSON logs faster than your application will fill it with data.

Sizing the VPS

StackPlan
One small app plus a proxyPulse — 4 GB, €6/mo
App, database and cacheCore — 8 GB, €10/mo
Several services, or building images on the boxForge — 16 GB, €16/mo
Production stack with headroomApex or Titan — 24–32 GB

Image builds are memory-hungry and disk-hungry in bursts. If builds are pushing you to a larger plan, build elsewhere and pull the image — that is exactly the pattern hourly instances are good for. Our DevOps and CI/CD page covers the pipeline side.

Where Docker is not the answer

  • A single static site. Nginx serving files needs no container, and the abstraction costs you troubleshooting clarity.
  • Databases you care deeply about. Running one in a container is fine and common; running one in a container with an unclear volume story is how people lose data. If in doubt, run the database on the host.
  • Very small instances. On a 2 GB machine the daemon plus images plus overhead is a meaningful fraction of the box.
  • When you need orchestration. Once you want multi-host scheduling and rolling updates, Compose is the wrong tool and Kubernetes or Swarm is the right one.

Frequently asked questions

Can I run Docker on any VPS?

On KVM, yes. On container-based virtualisation it is often restricted or unreliable because you are nesting containers. Check the virtualisation type before buying.

How much overhead does Docker add?

Very little CPU or memory for the containers themselves — they are processes with namespaces, not virtual machines. The daemon uses a few hundred megabytes; images and logs use disk, which is the resource to watch.

Should the database live in a container?

It works well with a named volume and a tested backup routine. Keep the data outside the container's lifecycle and dump it with the database's own tooling — see automating backups.

Does Docker bypass my firewall?

It can. Docker manipulates iptables directly, so a published port may be reachable despite a ufw rule. Bind to 127.0.0.1 and verify with ss -lntup.

Getting started

Everything above runs on a voxa.host KVM VPS with full root access, deployed in under a minute across twelve locations. At €0.0056/hr for the entry plan, building the stack on a throwaway instance first is the cheapest way to find the mistakes.

Ready in under a minute

Deploy your first server now.

No contracts, no minimums. Start on an Ion KVM VPS at €0.0063 an hour and move to a monthly bare-metal server the day you outgrow it.

$ voxa deploy --plan ion --location amsterdam

KVM VPS billed hourly, capped monthly · Dedicated billed monthly · No setup fee

Included on every plan
Free IPv4 + IPv6
Every VPS
Unmetered traffic
1–10 Gbps
DDoS mitigation
2.5 Tbps
Root / IPMI access
Included
Setup fee
€0.00
Minimum term
None