Deploy Docker on Vultr VPS: Complete 2026 Setup Guide

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.

Deploy Docker on Vultr VPS: Complete 2026 Setup Guide — featured image showing Docker whale logo with terminal and Vultr VPS branding
Deploy Docker on Vultr VPS: Complete 2026 Setup Guide — featured image showing Docker whale logo with terminal and Vultr VPS branding
🛠️ Free tools for this guide: Docker RAM Calculator (size your containers before picking a plan) · Dockerfile Generator (generate a production-ready Dockerfile for your stack)

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.

vultr

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-by-step diagram showing Docker Engine installation on Vultr VPS Ubuntu 24.04 — 5 steps from apt update to docker --version verification with expected terminal output
Step-by-step diagram showing Docker Engine installation on Vultr VPS Ubuntu 24.04 — 5 steps from apt update to docker –version verification with expected terminal output

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
Docker Compose architecture diagram on Vultr VPS — Nginx reverse proxy on ports 80/443 routing internal traffic to Node.js app container on port 3000 via Docker bridge network
Docker Compose architecture diagram on Vultr VPS — Nginx reverse proxy on ports 80/443 routing internal traffic to Node.js app container on port 3000 via Docker bridge network

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.

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
Diagram showing Docker UFW firewall bypass — Docker
Diagram showing Docker UFW firewall bypass — Docker’s nat table intercepting traffic before UFW INPUT chain, and the DOCKER-USER chain fix that enforces custom rules before Docker’s own forwarding logic

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.

vultr

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.

New to Vultr? Sign up through our link and get $300 free credit — valid for 30 days, limited-time offer. Claim your credit →

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top