This post contains affiliate links. If you purchase through our links, we may earn a commission at no extra cost to you.
Deploy Docker on Vultr VPS: Complete 2026 Setup Guide
Running Docker on a Vultr VPS is straightforward — until it isn’t. Most guides get you through the install and call it a day, leaving out two things that will bite you in production: the Docker + UFW firewall bypass that silently exposes your containers to the internet, and the correct install method that actually keeps Docker up to date. I’ve set this up enough times to know which steps matter. This guide covers the full path: choosing the right plan, installing Docker Engine properly, configuring Docker Compose V2, adding swap for low-RAM instances, and locking down your firewall the right way.

Why Vultr for Docker Workloads?
Not all VPS providers are equal when you’re running containerized apps. Storage I/O matters more than most people realize for Docker — every container image pull, build layer, and database volume write touches disk. Vultr’s NVMe SSD achieves roughly 118,000 combined 4K IOPS, compared to ~54,000 IOPS on DigitalOcean’s equivalent plan. That’s a 2.2x difference in disk throughput at the same price point, which shows up in faster container startup, snappier database queries inside containers, and noticeably quicker image builds.
The High Performance AMD plans run on AMD EPYC-Genoa at 3.25 GHz (Geekbench 6 single-core score: 1,926), which is strong for Docker workloads that benefit from high single-thread performance. Provisioning takes under 60 seconds, so you’re not waiting around.
our full Vultr benchmark review
Which Vultr Plan Should You Pick for Docker?
Here’s the current plan lineup relevant to Docker deployments, as of June 2026:
| Plan | vCPU | RAM | Storage | Bandwidth | Price/mo |
|---|---|---|---|---|---|
| Regular Performance (IPv6 only) | 1 | 0.5 GB | 10 GB SSD | 0.5 TB | $2.50 |
| High Performance AMD | 1 | 1 GB | 25 GB NVMe | 2 TB | $6.00 |
| High Frequency (Intel Xeon) | 1 | 1 GB | 32 GB NVMe | 1 TB | $6.00 |
| Optimized Cloud Compute GP | 1 | 4 GB | 30 GB NVMe | 4 TB | $30.00 |
| VX1 General Purpose | 2 | 8 GB | 120 GB NVMe | 5 TB | ~$54/mo |
Prices last checked: 2026-06-21 — Vultr Pricing Page
Skip the $2.50 plan entirely — 0.5 GB RAM isn’t enough to run the Docker daemon plus any meaningful container. The $6/mo High Performance AMD is the right entry point for one or two lightweight containers (add swap — more on that below). For a real Docker Compose stack with a database, Nginx, and an app service, I’d go straight to the $12–$30 range to avoid headaches.
see the full Vultr pricing breakdown
Quick note on GPU instances: Don’t use this guide for Vultr Cloud GPU instances. Those come with Docker pre-installed alongside the NVIDIA Container Toolkit, and manually installing Docker on them can cause conflicts.
Step 1 — Provision Your Vultr VPS
Log in to the Vultr console, click Deploy Server, and configure:
- Server type: Cloud Compute — High Performance
- Location: Pick the region closest to your users
- OS: Ubuntu 24.04 LTS (what this guide targets)
- Plan: $6/mo or higher (1 GB RAM minimum)
- Additional Features: Enable “IPv6” if needed; optionally check “Backups” for peace of mind
Vultr also offers a Marketplace Docker App — it auto-installs Docker at deploy time. It’s convenient for a quick test environment, but it installs a pinned Docker version you can’t easily update with apt upgrade. For anything running in production, the manual APT method below gives you full control.
Once deployed, SSH in as root:
ssh root@YOUR_SERVER_IP
Step 2 — Install Docker Engine (The Right Way)
Don’t use apt install docker.io. Ubuntu’s default apt repository ships an outdated Docker version without Docker Compose V2. Don’t use snap either — it adds overhead and causes permission issues with bind mounts.
The correct method uses Docker’s official APT repository. Here’s the full sequence for Ubuntu 24.04:
Update the system and install dependencies
sudo apt update && sudo apt upgrade -y
sudo apt install apt-transport-https ca-certificates curl software-properties-common -y
Add Docker’s official GPG key
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
Add Docker’s APT repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Install Docker Engine, CLI, and plugins
sudo apt update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
This one command installs everything you need: Docker Engine (docker-ce), the CLI (docker-ce-cli), the container runtime (containerd.io), multi-platform build support (docker-buildx-plugin), and Docker Compose V2 (docker-compose-plugin).
Enable Docker and verify
sudo systemctl enable docker
sudo systemctl status docker
sudo docker --version
Don’t skip systemctl enable docker. Without it, Docker won’t restart after a VPS reboot — a common gotcha that surfaces at the worst possible time.
Run Docker without sudo
sudo usermod -aG docker $USER
newgrp docker
Log out and back in for the group change to apply system-wide.

Step 3 — Add Swap for 1 GB RAM Plans
If you’re on the $6/mo plan (1 GB RAM), add a swap file before running containers. Without it, Docker can OOM-kill containers — or the daemon itself — under memory pressure.
# Create a 2 GB swap file
sudo fallocate -l 2G /swapfile
# Lock down permissions
sudo chmod 600 /swapfile
# Initialize and enable
sudo mkswap /swapfile
sudo swapon /swapfile
# Make it survive reboots
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# Reduce swappiness to protect NVMe write endurance
sudo sysctl vm.swappiness=10
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
# Verify
free -h
💡 Tip: Unsure how much swap your server needs? Our free Linux Swap Calculator recommends a size based on your RAM.
Setting vm.swappiness=10 tells the kernel to use swap only when RAM is above 90% capacity. Vultr’s NVMe handles swap well, but keeping swappiness low reduces unnecessary write wear on the SSD.
Step 4 — Docker Compose V2: Commands and a Real Example
Docker Compose V2 is already installed from the docker-compose-plugin package above — no separate download needed. Verify it:
docker compose version
Note the syntax: docker compose (space, no hyphen). The old docker-compose hyphenated binary is deprecated and isn’t installed by default in 2026. If you see scripts using it, update them.
Key Docker Compose Commands
| Command | What it does |
|---|---|
docker compose up -d |
Start all services in the background |
docker compose down |
Stop and remove containers and networks |
docker compose stop |
Halt services (containers preserved) |
docker compose ps |
Show running service status |
docker compose logs -f |
Stream live logs |
docker compose pull |
Update all images to latest |
Real-World Example: Nginx + Node.js App Stack
Here’s a working compose.yaml (Docker Compose V2 prefers this name over docker-compose.yml, though both work):
version: "3.9"
services:
nginx:
image: nginx:latest
container_name: nginx_proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./certbot/www:/var/www/certbot:ro
depends_on:
- app
restart: always
app:
image: node:20-alpine
container_name: my_app
working_dir: /usr/src/app
volumes:
- ./app:/usr/src/app
expose:
- "3000"
environment:
- NODE_ENV=production
restart: always
networks:
default:
driver: bridge
The app service uses expose (not ports) — it’s only reachable internally from nginx, not directly from the internet. Nginx handles the public-facing traffic. Start the whole stack with:
docker compose up -d

Step 5 — Fix the Docker UFW Firewall Bypass (Critical)
This is the section most Docker-on-VPS guides skip, and it’s the one that matters most for security.
Here’s the problem: Docker manipulates iptables directly to expose container ports. When you publish a container with -p 80:80, Docker adds rules in the nat table that route traffic before it ever reaches UFW’s INPUT chain. The result: a container with -p 80:80 is publicly accessible from the internet even if UFW has no rule allowing port 80. UFW’s status output will say port 80 is blocked. The container is still reachable. This isn’t a bug Docker plans to fix — it’s by design.
When I first set up a Docker stack on a fresh VPS with UFW enabled, I assumed the firewall was protecting my backend services. It wasn’t. Running a port scan showed they were fully exposed. Don’t make the same assumption.
Fix Option 1 — DOCKER-USER iptables Chain (Recommended)
Docker intentionally reserves the DOCKER-USER chain for sysadmin rules. Rules added here run before Docker’s own rules and survive Docker restarts:
# Drop all incoming traffic to Docker containers by default
sudo iptables -I DOCKER-USER -j DROP
# Allow traffic from your trusted IP or CIDR range
sudo iptables -I DOCKER-USER -s YOUR_IP_OR_CIDR -j ACCEPT
# Allow established and related connections (so responses get through)
sudo iptables -I DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Check your rules
sudo iptables -L DOCKER-USER -n -v
💡 Tip: Prefer a visual approach? You can build these rules with our free Firewall Rule Builder — it generates ufw and iptables commands right in your browser.
Make these rules survive reboots:
sudo apt install -y iptables-persistent
sudo netfilter-persistent save
Fix Option 2 — Bind Containers to Localhost Only
For services that shouldn’t be directly internet-facing (databases, internal APIs), bind to localhost instead of all interfaces:
# Instead of -p 80:80 (public), use:
docker run -d -p 127.0.0.1:8080:80 nginx
In compose.yaml:
ports:
- "127.0.0.1:8080:80"
Then expose those services through a reverse proxy (Nginx or Caddy on the host) that you control explicitly. This approach is cleaner for multi-service setups.
Fix Option 3 — ufw-docker Utility
If you prefer a tool that handles the integration automatically:
sudo wget -O /usr/local/bin/ufw-docker \
https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
sudo chmod +x /usr/local/bin/ufw-docker
sudo ufw-docker install
sudo systemctl restart ufw
# Then selectively allow containers
sudo ufw-docker allow nginx 80
Basic UFW Baseline (Do This First)
Before any of the above fixes, set a sane UFW baseline:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

Common Pitfalls at a Glance
| Pitfall | Consequence | Fix |
|---|---|---|
apt install docker.io or snap |
Outdated Docker, no Compose V2 | Use Docker’s official APT repo |
docker-compose (hyphenated) |
Command not found — deprecated binary | Use docker compose (V2 plugin) |
| Docker bypasses UFW | Containers publicly exposed despite firewall | DOCKER-USER chain or localhost binding |
| 1 GB RAM with no swap | OOM kills daemon or containers | Add 2 GB swap file |
Skipping systemctl enable docker |
Docker stops after VPS reboot | Always enable the service |
Exposing all ports with -p X:X |
Accidental public exposure of internal services | Use expose for internal services; ports only for public ones |
Frequently Asked Questions
Can I use the Vultr Marketplace Docker app instead of installing manually?
Yes, but with a trade-off. The Marketplace Docker App installs Docker automatically at deploy time and is convenient for quick experiments. The catch is it installs a pinned version — you can’t update Docker cleanly through apt upgrade. For a production server or any environment you’ll maintain long-term, use the manual APT repository method so you stay current.
Does Docker work on Vultr’s $6/mo plan?
It runs, but only with a swap file. The Docker daemon itself uses roughly 100–200 MB of RAM, leaving less than 800 MB for containers on a 1 GB instance. Adding a 2 GB swap file (as covered in Step 3) prevents OOM crashes for lightweight workloads. If you’re running a database container alongside an app, the $12/mo plan (2 GB RAM) is a more comfortable fit.
Why doesn’t UFW block Docker containers?
Docker bypasses UFW by writing rules directly into the kernel’s nat iptables table before UFW’s rules are evaluated. This is intentional behavior documented by Docker — the firewall chains Docker uses (DOCKER, DOCKER-ISOLATION-STAGE-*) operate at a different priority than UFW’s INPUT/OUTPUT chains. The fix is using the DOCKER-USER chain, which Docker runs your custom rules through before applying its own forwarding logic.
Is Docker Compose V2 included or do I need to install it separately?
It’s included. The docker-compose-plugin package installed in Step 2 gives you Docker Compose V2 as a Docker CLI plugin. Run docker compose version to confirm. The old standalone docker-compose Python binary is deprecated and no longer installed by default — update any old scripts to use docker compose (with a space).
What about running Docker on Vultr for WordPress?
You can, but a full LEMP stack in Docker on a 1 GB VPS is tight. If you want WordPress on Vultr without container complexity, the traditional setup is more practical for small sites. see our WordPress on Vultr guide for a direct comparison
Final Verdict: Vultr + Docker Is a Strong Combination
Vultr’s NVMe I/O and AMD EPYC compute make it one of the better-value platforms for Docker workloads — particularly if your stack involves a database container where the 118K IOPS figure matters. The setup itself isn’t complex, but three things will determine whether it works reliably in production: using Docker’s official APT repo (not docker.io), adding swap on 1 GB instances, and fixing the UFW bypass before you put anything sensitive behind a container.
For lightweight containerized apps, the $6/mo High Performance AMD plan is a credible starting point. For a full Compose stack — Nginx, app, and a database — go with the $12 or higher. New Vultr accounts currently get $100 in free credits, which gives you plenty of runway to test your setup before committing.
If you’re evaluating Vultr against other providers for Docker or VPS work generally, our cheap VPS roundup covers how it stacks up on price-to-performance across the sub-$10 tier.

