Docker Universal Template: Easy Deployment for Any Automation Tool

💡 AD: DigitalOcean $200 Free Credit (60 Days) Claim via Our Link →

I've run more than a dozen self-hosted services on my VPS. Early on, every deployment meant starting from scratch. Eventually I got tired of the repetition and built a set of reusable templates. Now, deploying a new service takes under 15 minutes from copying the template to having it running. Here's the complete process.


Why Docker Compose is the best approach for VPS self-hosting

Complete environment isolation means each service runs in its own container without interfering with anything else. Versioning is controlled—locking an image tag makes upgrades and rollbacks fully traceable. Migration is straightforward: compress the stack directory with its data volumes, decompress on the new server, done.

Paired with Nginx Proxy Manager or Traefik, domain binding and HTTPS certificate issuance become graphical operations—no manual Nginx configuration file editing required.

For running multiple self-hosted services long-term on a VPS, Docker Compose is the most cost-effective solution available.


Step 1: Install Docker

For Ubuntu/Debian, installing from the official Docker repository is the most stable approach:

# Remove old versions
sudo apt-get remove docker docker-engine docker.io containerd runc -y

# Add official repository
sudo apt update && sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] 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
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Verify the installation:

docker --version
docker compose version

Add your user to the docker group to avoid needing sudo every time:

sudo usermod -aG docker $USER
newgrp docker

Step 2: Universal Docker Compose template

I keep all services under /opt/stacks/service-name/ for consistent management and easy backups.

docker-compose.yml (core template):

version: "3.8"

services:
  main:
    image: xxx/yyy:tag  # Replace with the target software image
    container_name: ${COMPOSE_PROJECT_NAME:-app}
    restart: unless-stopped
    ports:
      - "${EXTERNAL_PORT:-8080}:80"  # Left side is host port — plan ahead to avoid conflicts
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
      # Add additional environment variables as required by the software
    volumes:
      - ./config:/config
      - ./data:/data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"] || exit 1
      interval: 30s
      timeout: 10s
      retries: 3

networks:
  default:
    name: ${COMPOSE_PROJECT_NAME}_net

Companion .env file—manage all variables here rather than scattering them through the compose file:

EXTERNAL_PORT=5683
TZ=Asia/Shanghai
PUID=1000
PGID=1000

Most software runs with just three or four lines changed from this template. It saves a significant amount of repeated configuration work.


Step 3: Standardized deployment process

Follow this sequence every time you deploy a new service—it's less error-prone:

# 1. Create the directory and enter it
mkdir -p /opt/stacks/service-name && cd /opt/stacks/service-name

# 2. Copy the template or download the official compose file
#    Adjust image, ports, and volumes as needed

# 3. Create the .env file with your configuration variables

# 4. Validate the compose file syntax (many people skip this — don't)
docker compose config

# 5. Start the service
docker compose up -d

# 6. Check startup logs to confirm everything is running correctly
docker compose logs -f

# 7. Open the relevant port in your cloud provider's security group and UFW

Step 4—docker compose config—is worth making a habit. It expands all template variables and displays the final configuration, making syntax errors and variable substitution problems immediately visible.


Step 4: Centralize domain management and SSL with Nginx Proxy Manager

Manually configuring Nginx and certificates is tedious. Nginx Proxy Manager (NPM) is the most low-maintenance solution available—a graphical interface that handles Let's Encrypt certificate issuance with a single click.

Deploy NPM:

version: '3.8'
services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "81:81"  # Admin panel — restrict access by IP in production
    environment:
      DB_MYSQL_HOST: db
      DB_MYSQL_USER: npm
      DB_MYSQL_PASSWORD: your_strong_password
      DB_MYSQL_NAME: npm
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    depends_on:
      - db

  db:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: stronger_root_password
      MYSQL_DATABASE: npm
      MYSQL_USER: npm
      MYSQL_PASSWORD: your_strong_password
    volumes:
      - ./mysql:/var/lib/mysql
docker compose up -d

Open http://server_IP:81. Default credentials are [email protected] / changemechange these immediately after first login.

Adding a new service afterward takes seconds: create a Proxy Host record in NPM, enter the domain name and container's internal address, enable SSL auto-issuance, and it's done.


Common operations quick reference

# Pull latest image and restart (most common for routine upgrades)
docker compose pull && docker compose up -d

# Stream live logs
docker compose logs -f

# Restart service
docker compose restart

# Stop and remove containers (data directory preserved)
docker compose down

# Open a shell inside the container for debugging
docker compose exec main bash

# Back up the entire service stack
tar -czf backup-$(date +%F).tar.gz /opt/stacks/service-name/

Troubleshooting common issues

Port already in use: Run sudo lsof -i:port_number to identify the occupying process, then either terminate it or change EXTERNAL_PORT in your .env file.

Permission errors: Data directory permissions are the most common pitfall. chown -R 1000:1000 ./data ./config resolves this in most cases.

Can't access from external network: Check in this order—cloud provider security group rules, UFW firewall rules, and whether the container is actually running (docker compose ps).

Container restarting repeatedly: Check docker compose logs for the error. In 90% of cases it's an incorrectly configured environment variable or a database connection problem.


Self-hosted tools worth running on a VPS in 2026

All of these deploy cleanly with the template above:

  • Photo management: Immich (the closest open-source equivalent to Google Photos)
  • Password management: Vaultwarden (lightweight Bitwarden-compatible server, minimal resource usage)
  • File management: Alist, Filebrowser
  • Media server: Jellyfin
  • Uptime monitoring: Uptime Kuma
  • RSS reader: FreshRSS
  • Knowledge base: BookStack, Outline
  • Bookmark manager: Linkding

Each of these has an actively maintained official Docker image. A few lines changed from the template above is all it takes to get any of them running. Once you're comfortable with this workflow, your VPS effectively becomes a private cloud platform.

← Previous
5 Best Free Open-Source Automation Tools to Run on VPS in 2026 (Better Than Paid Ones)
Next →
5 things that novices are advised to do after getting a VPS

💬 Comments

150 characters left

No comments yet. Be the first!

← Back to Articles