Docker has become the de facto standard for deploying applications in 2026. This guide walks you through setting up a complete Docker environment on Vultr — from initial server configuration to production-ready container orchestration with Docker Compose.

Why Run Docker on Vultr?

Vultr's high-frequency compute instances paired with all-SSD storage deliver exceptional I/O performance for container workloads. Whether you're running a single Node.js application or orchestrating a multi-service architecture, Vultr's $5/month plan provides enough resources to get started. Compare this to managed container services that can cost 5-10x more for equivalent resources.

Prerequisites

  • A Vultr account (use our referral link for $100 free credit)
  • A Vultr VPS running Ubuntu 22.04 LTS or 24.04 LTS
  • SSH access to your instance
  • Basic command line familiarity

Step 1: Initial Server Setup

First, ensure your system is up to date:

sudo apt update && sudo apt upgrade -y

Create a new sudo user (avoid using root for daily operations):

sudo adduser dockeradmin
sudo usermod -aG sudo dockeradmin

Set up SSH key-based authentication for enhanced security:

mkdir -p ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys  # Paste your public key

Step 2: Install Docker Engine

Docker provides an official installation script for Ubuntu. This is the fastest way to get started:

curl -fsSL https://get.docker.com | sh

This script installs Docker, creates the docker group, and starts the Docker daemon. Verify the installation:

docker --version
# Output: Docker version 27.x.x, build xxxxxx

sudo systemctl status docker

Add your user to the docker group to run Docker commands without sudo:

sudo usermod -aG docker dockeradmin
# Log out and back in for group membership to take effect

Step 3: Install Docker Compose

Docker Compose V2 is included with Docker Desktop but on Linux servers, you need to install it separately. For production use, download the latest stable release:

# Install Docker Compose v2
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

# Verify
docker compose version
# Output: Docker Compose version v2.x.x

Step 4: Configure Docker for Production

Production Docker setups require additional configuration for security and performance.

Configure Docker Daemon

Create or edit the Docker daemon configuration:

sudo nano /etc/docker/daemon.json

Add this production-optimized configuration:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "live-restore": true,
  "default-address-pools": [
    {"base": "172.17.0.0/12", "size": 24}
  ]
}

Restart Docker to apply changes:

sudo systemctl restart docker

Configure Firewall (UFW)

Only allow necessary ports. Docker exposes several ports by default — configure UFW to restrict access:

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Only if you need direct Docker API access:
# sudo ufw allow 2375/tcp  # NOT recommended without TLS

Step 5: Deploy Your First Application

Let's deploy a real-world application — a Nginx reverse proxy with a simple web app. Create the project structure:

mkdir -p ~/projects/webapp
cd ~/projects/webapp
nano docker-compose.yml

Add this Docker Compose configuration:

version: '3.8'

services:
  web:
    image: nginx:alpine
    container_name: webapp_nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./html:/usr/share/nginx/html:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    networks:
      - webapp_network
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost/"]
      interval: 30s
      timeout: 10s
      retries: 3

  app:
    image: node:20-alpine
    container_name: webapp_api
    working_dir: /app
    command: npx http-server -p 3000
    restart: unless-stopped
    networks:
      - webapp_network
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/"]
      interval: 30s
      timeout: 10s
      retries: 3

networks:
  webapp_network:
    driver: bridge

Create the Nginx configuration:

mkdir -p ~/projects/webapp/html
nano ~/projects/webapp/nginx.conf
events {
    worker_connections 1024;
}

http {
    server {
        listen 80;
        server_name localhost;

        location / {
            root /usr/share/nginx/html;
            index index.html;
        }

        location /api {
            proxy_pass http://app:3000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_cache_bypass $http_upgrade;
        }
    }
}

Launch the application:

cd ~/projects/webapp
docker compose up -d

# Check status
docker compose ps

# View logs
docker compose logs -f

Step 6: Secure Your Docker Setup

Production containers need proper security hardening:

Use Non-Root Users in Containers

Modify your docker-compose.yml to run as non-root:

services:
  web:
    image: nginx:alpine
    user: "101:101"  # nginx user UID/GID

Limit Container Resources

services:
  app:
    image: node:20-alpine
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

Enable Docker Content Trust

export DOCKER_CONTENT_TRUST=1

This prevents pulling unverified images from Docker Hub.

Step 7: Set Up Automated Backups

Container data persists in volumes. Set up a backup strategy using a simple cron job:

# Create backup script
sudo nano /usr/local/bin/docker-backup.sh
#!/bin/bash
BACKUP_DIR="/backup/docker"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p $BACKUP_DIR

# Backup all volumes
for volume in $(docker volume ls -q); do
    docker run --rm \
        -v ${volume}:/volume \
        -v ${BACKUP_DIR}:/backup \
        alpine \
        tar czf /backup/${volume}_${DATE}.tar.gz -C /volume .
done

# Keep only last 7 days
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
sudo chmod +x /usr/local/bin/docker-backup.sh
# Add to crontab
sudo crontab -e
0 2 * * * /usr/local/bin/docker-backup.sh >> /var/log/docker-backup.log 2>&1

Real-World Example: Deploying a Node.js API

Here's a practical example deploying an Express.js REST API with PostgreSQL:

# docker-compose.yml for Node.js + PostgreSQL
services:
  api:
    build: ./api
    container_name: express_api
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    ports:
      - "3000:3000"

  db:
    image: postgres:16-alpine
    container_name: postgres_db
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

Build and deploy:

docker compose up -d --build
docker compose logs -f api

Monitoring Your Containers

Production deployments need monitoring. Add Portainer for a web-based Docker management interface:

docker volume create portainer_data

docker run -d \
  --name portainer \
  --restart unless-stopped \
  -p 9000:9000 \
  -p 9443:9443 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:latest

Access Portainer at https://your-vultr-ip:9443. Note: Always secure this with strong passwords and consider restricting access via firewall.

Troubleshooting Common Issues

Docker Daemon Not Starting

sudo journalctl -u docker -f
sudo dockerd --debug  # Run in foreground to see errors

Permission Denied Errors

Usually caused by incorrect volume permissions. Fix by adjusting host directory ownership:

sudo chown -R $(id -u):$(id -g) ./your_volume

Out of Disk Space

Docker images can consume significant space. Regular cleanup helps:

docker system prune -a
docker volume prune

Performance Tuning for Vultr

Vultr's SSD storage performs best with Docker when you optimize the storage driver. The overlay2 driver (default on Ubuntu) is already optimized, but ensure you're not using devicemapper on old kernels.

For high-throughput applications, consider Vultr's High Frequency compute instances — they deliver up to 3.8GHz CPU speeds, significantly improving container startup times and I/O performance.

Conclusion

A proper Docker setup on Vultr gives you a powerful, cost-effective container platform. Starting at $5/month, Vultr's VPS instances provide excellent value compared to managed container services. The key takeaways:

  • Always use Docker Compose for multi-container applications
  • Configure resource limits to prevent container runaway
  • Implement regular backups of persistent volumes
  • Use health checks for automatic container recovery
  • Monitor with tools like Portainer or Prometheus

Ready to deploy? Get started with a free Vultr account and use our other tutorials to build your infrastructure.

For sports enthusiasts interested in combining tech with recreation, explore Cloudbet Guide for expert betting insights.

Start Your Docker Journey on Vultr

Get $100 free credit when you sign up via our link:

Deploy Docker on Vultr Now