📑 Daftar Isi
- Why Most Docker Setups on a VPS Go Wrong
- Step 1 — Update and Create a Deploy User
- Step 2 — Install Docker Engine from the Official Repo
- Step 3 — Grant Your User Docker Access
- Step 4 — Smoke Test
- Step 5 — Run Your First Real Container
- Step 6 — Set Resource Limits Before You Need Them
- Step 7 — Persist Your Data or Lose It
- Step 8 — Harden Docker in Ten Minutes
- Step 9 — Graduate to Docker Compose
- Troubleshooting Table
- Quick Command Reference
- FAQ
Skip the fluff. You’re here because you want Docker running on your VPS today, not next week, and definitely not after a 2000-word history lesson about containers. Fair enough. I’ve done this exact setup on more servers than I can count, and here’s the exact playbook I use, in the exact order I use it. Follow along and you’ll be done in under twenty minutes.
One note before we start. This guide is written for a RHEL-family VPS — Rocky Linux 9 or AlmaLinux 9 — because that’s what most of my production boxes run. If you’re on Ubuntu or Debian, the logic is identical but the package manager differs; I’ll flag those spots as we go. Everything below is tested on Rocky Linux 9 with Docker Engine 26.
Why Most Docker Setups on a VPS Go Wrong
Let me be real with you: Docker is not the hard part. Installing it is a handful of commands. What actually wrecks people is treating containers like magical bubbles that can’t touch the host. They can. Containers share your kernel, your CPU, your RAM, and your disk. I’ve watched a single container with a memory leak drag an entire production host into swap death because nobody bothered to set –memory. I’ve seen /var fill up overnight because default Docker logging has no size cap. Every single time, the fix was configuration, not the application.
The other side of the coin is what Docker genuinely fixes for you. If you’ve ever fought with “works on my machine”, or hesitated to install a second app because it might break the first one, Docker removes that entire category of pain. Each app ships with its own environment. You can run a database, a cache, and a web app side by side on one VPS and they never touch each other’s dependencies. That’s a big deal when you’ve got a single server and you want it to earn its keep.
The damage side matters too, because in production the cost is real. Downtime when a container eats all the RAM. Data loss when someone deletes a container that held the only copy of a database. Security holes when containers run as root with every Linux capability enabled. None of these are exotic — I’ve seen each one happen. And all of them are preventable with about fifteen minutes of setup. That’s the whole point of this guide: get it right the first time so you never have to learn these lessons the hard way.

Step 1 — Update and Create a Deploy User
Never run your day-to-day Docker commands as root. Create a deploy user and do everything from there. And while you’re at it, if you haven’t locked down SSH yet, do that first — you don’t want to be configuring containers on a box that already has a guest. See securing SSH on your VPS.
sudo dnf update -y
sudo useradd -m deployer
sudo passwd deployer
sudo usermod -aG wheel deployer
sudo su - deployer
Step 2 — Install Docker Engine from the Official Repo
Do not install the distro-bundled docker package. The official repository gets updates fast and is the only supported path. Three commands to add the repo, then install.
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker
Verify the install:
docker version
docker compose version
If you’re on Ubuntu or Debian instead, you’d add the equivalent apt repository from download.docker.com and install the same packages — the enable and start commands are identical.
Step 3 — Grant Your User Docker Access
Add your deploy user to the docker group so you don’t need sudo on every command:
sudo usermod -aG docker deployer
Log out and back in, or run newgrp docker, so the group membership takes effect.
Step 4 — Smoke Test
docker run --rm hello-world
If you see “Hello from Docker!”, you’re good to go. If you get a permission error, you didn’t log out and back in.
Step 5 — Run Your First Real Container
docker run -d --name nginx --restart unless-stopped -p 80:80 nginx:alpine
curl http://localhost/
Hitting the Nginx welcome page means the whole stack works. If this box is reachable from the internet, hold off on keeping that port open until the firewall step below — a brand-new Nginx on port 80 is a magnet for scanners.
Step 6 — Set Resource Limits Before You Need Them
Do this now, not after an incident. A container with no limit can consume the entire host and take every other service down with it.
docker run -d --name app --memory 256m --cpus 0.5 -p 8000:8000 myapp:latest
Check it with docker stats and you’ll see the limits enforced. The container now physically cannot take more than 256MB of RAM or half a CPU core, no matter how leaky the app gets.
Step 7 — Persist Your Data or Lose It
Containers are disposable by design. Anything written inside a container dies with it. For data that matters — databases, uploads, configs — use a volume. Two options: named volumes (managed by Docker) and bind mounts (a host directory). Databases: named volume. Files you edit from the host: bind mount.
docker volume create dbdata
docker run -d --name db --restart unless-stopped
-v dbdata:/var/lib/mysql
-e MARIADB_ROOT_PASSWORD=change-me
mariadb:11
Delete that container and the data stays in the volume. Create a new one pointing at the same volume and you’re back in business. And schedule backups for those volumes — volumes aren’t a backup strategy, they’re just a place to keep data. See automatic VPS backups with rsync.

Step 8 — Harden Docker in Ten Minutes
Skip this and you’ll regret it eventually. Ten minutes, no excuses:
- Run containers as a non-root user when possible, via –user or the USER directive in the Dockerfile.
- Drop Linux capabilities you don’t need: –cap-drop ALL.
- Use –read-only for containers that don’t need to write to their own filesystem.
- Cap log sizes in /etc/docker/daemon.json so /var never fills up (config below).
- Only publish the ports that actually need to be public; keep internal services on a Docker network instead of the host network.
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
sudo systemctl restart docker
For the firewall, on RHEL-family that’s firewalld by default. Open only the ports you need — typically 80 and 443 — and nothing else. There’s a full walkthrough in our firewall setup guide for VPS.
Step 9 — Graduate to Docker Compose
One container is fine, but real applications have a web tier, an app tier, and a database. That’s Compose territory: one YAML file, one command, whole stack up.
services:
app:
image: myapp:latest
ports:
- "8000:8000"
environment:
DB_HOST: db
depends_on:
- db
restart: unless-stopped
db:
image: mariadb:11
environment:
MARIADB_ROOT_PASSWORD: change-me
volumes:
- dbdata:/var/lib/mysql
restart: unless-stopped
volumes:
dbdata:
docker compose up -d
docker compose ps
docker compose logs -f
Same file works on any other server — that reproducibility alone is worth switching to Compose. Don’t commit real secrets into this file, though. Use environment variables or Docker secrets. Once the stack is up, add Netdata monitoring so you actually see what’s eating resources.
Troubleshooting Table
The most common failures, and the fastest fix for each. If you’re seeing sustained high load even after applying these, check our guide on troubleshooting high load on a VPS.
| Symptom | Likely Cause | Quick Fix |
|---|---|---|
| docker: permission denied | User not in the docker group | sudo usermod -aG docker $USER, then log out and back in |
| port is already allocated | Another process holds the port | sudo ss -tlnp and free the port or change the mapping |
| Container stuck in Restarting | The app inside crashes on start | docker logs <name>, fix the error, redeploy |
| /var is 100% full | Un-capped container logs | Add log limits, then docker system prune -a |
| Host slow, swap full | No resource limits on containers | Add –memory and –cpus, restart the containers |
| docker pull fails with timeout | DNS or registry connectivity | Check resolv.conf and try pulling a smaller image |
Quick Command Reference
| Command | What It Does |
|---|---|
| docker ps -a | List all containers, running and stopped |
| docker images | List downloaded images |
| docker logs <name> | Show a container’s logs |
| docker exec -it <name> sh | Open a shell inside a container |
| docker stop <name> | Stop a container |
| docker rm <name> | Delete a container |
| docker stats | Live CPU and memory usage per container |
| docker system prune -a | Remove unused images and containers |
FAQ
Q: Is Docker safe to run on a 1GB RAM VPS?
Yes, if you’re disciplined. The engine itself is light — the apps inside it are what cost RAM. On 1GB, run one or two small containers with –memory limits and you’ll be fine. Ten containers on 1GB is a crash waiting to happen.
Q: When should I pick Docker over a VM?
Docker when you want to run multiple apps on one server with minimal overhead and fast start times. A VM when you need full kernel isolation or a completely different OS. They’re tools for different jobs.
Q: Is Docker Compose mandatory?
No, but once your app has more than one service, it’s the difference between three commands and one. The same compose file redeploys on any server, which makes migrations trivial.
Q: Do I need Docker Desktop on my VPS?
No. Docker Desktop is the GUI app for macOS and Windows. On a VPS you install Docker Engine — which is exactly what we did above — and you interact with it through the CLI.
Q: Can a container be hacked, and what limits the damage?
Yes, containers are not a security boundary. Running as non-root, dropping capabilities, and using read-only filesystems all shrink the blast radius if something gets exploited.
That’s the playbook. Go run it — and before you close this tab, run the checklist: resource limits set? Restart policy set? Firewall only exposing what needs to be public? Backups scheduled? Four yeses and you’re done.