Guides

Building Self-Hosting with open-source tools

This guide outlines the implementation of a production-ready self-hosting environment on a single VPS. It focuses on security hardening, automated SSL termination using Caddy, and establishing a maintenance lifecycle using Docker and Watchtower to minimize manual intervention and ensure data sovereignty.

45 minutes6 steps
1

VPS Hardening and SSH Configuration

Before deploying services, secure the host OS. Disable password-based authentication and move the SSH port to mitigate automated brute-force attacks. Ensure the system is updated to the latest security patches.

setup_host.sh
apt update && apt upgrade -y
# Change SSH port and disable root password login
sed -i 's/#Port 22/Port 2222/' /etc/ssh/sshd_config
sed -i 's/PermitRootLogin yes/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
systemctl restart ssh

⚠ Common Pitfalls

  • Ensure your SSH key is added to authorized_keys before disabling password login to avoid being locked out.
  • Remember to specify the new port (e.g., ssh -p 2222) in subsequent connections.
2

Firewall Configuration with UFW

Strictly control inbound traffic. Only allow traffic on your custom SSH port, HTTP (80), and HTTPS (443). By default, Docker bypasses UFW rules by manipulating iptables; we must ensure the firewall is active before Docker installation.

firewall.sh
ufw default deny incoming
ufw default allow outgoing
ufw allow 2222/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

⚠ Common Pitfalls

  • Enabling UFW without allowing your SSH port will terminate your current session and block future access.
3

Docker Engine and Compose Installation

Install the official Docker Engine. Avoid using the default snap or apt versions provided by the OS distribution as they are often outdated and can cause issues with modern Docker Compose features.

install_docker.sh
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
usermod -aG docker $USER

⚠ Common Pitfalls

  • You must log out and back in for the group changes to take effect if you want to run docker commands without sudo.
4

Deploy Caddy as a Reverse Proxy

Caddy simplifies self-hosting by providing automatic TLS through Let's Encrypt. Use a dedicated Docker network to allow Caddy to communicate with other containers using their service names.

docker-compose.yml
version: '3.8'
services:
  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
networks:
  default:
    name: proxy_network
volumes:
  caddy_data:
  caddy_config:

⚠ Common Pitfalls

  • Ensure your DNS 'A' records point to the VPS IP before starting Caddy, or the ACME challenge will fail.
5

Automate Security Patches with Watchtower

Self-hosting requires keeping software updated to prevent exploits. Watchtower monitors running containers and automatically pulls the latest images and restarts the service if a change is detected.

deploy_watchtower.sh
docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower \
  --cleanup --interval 86400

⚠ Common Pitfalls

  • Automated updates can occasionally break services if the upstream image introduces breaking changes. Always pin critical services to major version tags (e.g., :1) rather than :latest.
6

Configure Off-site Volume Backups

Data on a single VPS is vulnerable to hardware failure or provider outages. Use a script to compress Docker volumes and sync them to an external S3-compatible storage or a secondary server.

backup.sh
#!/bin/bash
BACKUP_PATH="/opt/backups"
DATE=$(date +%Y%m%d)
tar -czf $BACKUP_PATH/data_$DATE.tar.gz /var/lib/docker/volumes/
# Example sync to remote storage (requires rclone configured)
rclone copy $BACKUP_PATH/data_$DATE.tar.gz remote:my-backups/

⚠ Common Pitfalls

  • Backing up active database files directly from /var/lib/docker/volumes can lead to corruption. Use pg_dump or mysqldump before running the file backup for database containers.

What you built

Your self-hosting environment is now secured and automated. By using Caddy for TLS, Watchtower for updates, and a strict firewall, you have minimized the operational overhead of managing your own infrastructure. Regularly test your backup restoration process to ensure business continuity.