This comprehensive guide will walk you through setting up a self-hosted environment on Ubuntu 20.04 or newer using Docker. You’ll learn how to secure your services, access them via WSL, and configure local DNS with Pi-hole for seamless access across your network.
Before diving into the technical details, let’s understand what we’re building: a personal cloud infrastructure that gives you complete control over your data and services while maintaining security and ease of access.
Introduction: Self-Hosting with Docker
Self-hosting allows you to run services on your own hardware instead of relying on third-party providers. Docker makes this process significantly easier by containerizing applications, ensuring consistency across environments, and simplifying deployment and updates.
Benefits of using Docker for self-hosting:
- Isolation: Each service runs in its own container
- Portability: Easy migration between hosts
- Consistency: Reproducible environments
- Security: Enhanced isolation compared to traditional installations
- Simplified updates: Update services with minimal downtime
- Resource efficiency: Lower overhead than virtual machines
instruction layout/flow
Prerequisites
Hardware Requirements
- A computer/server running Ubuntu 20.04 LTS or newer
- Minimum 4GB RAM (8GB+ recommended for multiple services)
- At least 50GB storage (SSD preferred)
- Stable network connection
Software/Network Requirements
- Ubuntu 20.04 LTS or newer (server or desktop edition)
- sudo privileges
- Internet connection
- Router with port forwarding capabilities (if you want external access)
- Basic command line knowledge
Required Permissions
- User with sudo access
- Ability to modify network settings
Installation Process
Step 1: Set Up the Base System
First, ensure your Ubuntu system is up-to-date:
sudo apt update && sudo apt upgrade -y
This updates your package lists and upgrades installed packages to their latest versions.
Step 2: Create Directory Structure
We’ll establish a consistent directory structure for our Docker data:
mkdir -p ~/docker_data/traefik/data ~/docker_data/traefik/config
mkdir -p ~/docker_data/portainer/data
mkdir -p ~/docker_data/pihole/data ~/docker_data/pihole/config
This creates the base directories for our core services. When adding more services later, you’ll follow the same pattern of ~/docker_data/service_name/{data,config}
.
Step 3: Install Docker
Let’s install Docker using the official repository:
# Uninstall old versions (if any)
sudo apt remove docker docker-engine docker.io containerd runc
Install required dependencies:
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release
Add Docker’s official GPG key:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
Set up the stable repository:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Install Docker Engine:
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io
Verify Docker installation:
sudo docker run hello-world
This should output a message confirming Docker is working correctly[^14][^15].
Step 4: Install Docker Compose
Docker Compose makes managing multi-container applications easier:
sudo apt install -y docker-compose-plugin
Verify the installation:
docker compose version
Step 5: Add Your User to the Docker Group (Optional)
This step allows you to run Docker commands without sudo:
sudo usermod -aG docker $USER
To apply this change, log out and log back in, or run:
newgrp docker
Test that you can run Docker without sudo:
docker run hello-world
Step 6: Set Up Traefik Reverse Proxy
Traefik will route traffic to different services and handle HTTPS certificates:
cd ~/docker_data/traefik
Create a network for Traefik:
docker network create proxy
Create an .env file for configuration:
cat > .env << 'EOF'
# Your domain
DOMAIN=home.local
# Your email for Let's Encrypt
EMAIL=your-email@example.com
EOF
Create traefik.yml configuration:
cat > config/traefik.yml << 'EOF'
global:
sendAnonymousUsage: false
api:
dashboard: true
insecure: false
entryPoints:
http:
address: ":80"
http:
redirections:
entryPoint:
to: https
scheme: https
https:
address: ":443"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: proxy
certificatesResolvers:
letsencrypt:
acme:
email: ${EMAIL}
storage: /etc/traefik/acme.json
httpChallenge:
entryPoint: http
EOF
Create a file for basic HTTP authentication for the Traefik dashboard:
sudo apt install -y apache2-utils
mkdir -p config/auth
htpasswd -c config/auth/.htpasswd admin
Enter a strong password when prompted.
Create docker-compose.yml:
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
traefik:
image: traefik:v2.10
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./config/traefik.yml:/etc/traefik/traefik.yml:ro
- ./config/auth/.htpasswd:/etc/traefik/auth/.htpasswd:ro
- ./data/acme.json:/etc/traefik/acme.json
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.entrypoints=https"
- "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN}`)"
- "traefik.http.routers.traefik.tls=true"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.middlewares=traefik-auth"
- "traefik.http.middlewares.traefik-auth.basicauth.usersfile=/etc/traefik/auth/.htpasswd"
- "traefik.http.services.traefik.loadbalancer.server.port=8080"
networks:
proxy:
external: true
EOF
Create the acme.json file for storing SSL certificates:
touch data/acme.json
chmod 600 data/acme.json
Start Traefik:
docker compose up -d
Verify Traefik is running:
docker ps | grep traefik
Step 7: Install Portainer for Container Management
Portainer provides a web UI to manage your Docker containers:
cd ~/docker_data/portainer
Create docker-compose.yml:
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data:/data
labels:
- "traefik.enable=true"
- "traefik.http.routers.portainer.rule=Host(`portainer.home.local`)"
- "traefik.http.routers.portainer.entrypoints=https"
- "traefik.http.routers.portainer.tls=true"
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
networks:
proxy:
external: true
EOF
Start Portainer:
docker compose up -d
Verify Portainer is running:
docker ps | grep portainer
Step 8: Set Up Pi-hole for DNS and Ad Blocking
Pi-hole will provide local DNS resolution and network-wide ad blocking:
cd ~/docker_data/pihole
Create docker-compose.yml:
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
pihole:
container_name: pihole
image: pihole/pihole:latest
restart: unless-stopped
hostname: pihole
networks:
- proxy
ports:
- "53:53/tcp"
- "53:53/udp"
environment:
TZ: 'America/New_York'
WEBPASSWORD: 'set-a-secure-password'
SERVERIP: '192.168.1.x' # Change to your server's IP address
volumes:
- ./config:/etc/pihole
- ./data:/etc/dnsmasq.d
cap_add:
- NET_ADMIN
labels:
- "traefik.enable=true"
- "traefik.http.routers.pihole.rule=Host(`pihole.home.local`)"
- "traefik.http.routers.pihole.entrypoints=https"
- "traefik.http.routers.pihole.tls=true"
- "traefik.http.services.pihole.loadbalancer.server.port=80"
networks:
proxy:
external: true
EOF
Be sure to change the placeholder values:
- Set a secure password for WEBPASSWORD
- Update the TZ value to your timezone
- Change SERVERIP to your server’s actual IP address
Start Pi-hole:
docker compose up -d
Verify Pi-hole is running:
docker ps | grep pihole
Configure your router to use Pi-hole as the DNS server, or configure individual devices to use your server’s IP address for DNS.
Security Considerations
Network Security
- Use dedicated networks for different service types:
# Create additional networks as needed
docker network create backend
docker network create database
- Restrict container access to only necessary ports:
# Example of restricting a container to only localhost access
ports:
- "127.0.0.1:8080:8080"
- Set up a firewall on your Ubuntu host:
sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw enable
Verify the firewall status:
sudo ufw status
Container Security
- Run containers with non-root users when possible. In your docker-compose.yml files:
user: "1000:1000" # Replace with your actual UID:GID
Find your UID:GID with:
id
- Use read-only file systems where possible:
volumes:
- ./config:/config:ro
- Limit container capabilities:
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
- Apply resource limits:
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
Authentication Security
- Use strong passwords for all services
- Implement basic authentication for services exposed through Traefik
- For critical services, consider using 2FA where available
Data Persistence
Volume Configuration
All data is stored in ~/docker_data
with a consistent structure:
~/docker_data/
service_name/
data/ # For service data
config/ # For configuration files
docker-compose.yml
Backup Procedures
Set up a simple backup script:
cat > ~/backup-docker.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/path/to/backup/location"
DATE=$(date +%Y-%m-%d)
BACKUP_FILE="docker_backup_$DATE.tar.gz"
# Create backup directory if it doesn't exist
mkdir -p $BACKUP_DIR
# Compress the docker_data directory
tar -czf $BACKUP_DIR/$BACKUP_FILE ~/docker_data
# Optional: Prune old backups (keep last 7)
find $BACKUP_DIR -name "docker_backup_*" -type f -mtime +7 -delete
echo "Backup completed: $BACKUP_DIR/$BACKUP_FILE"
EOF
Make the script executable:
chmod +x ~/backup-docker.sh
Set up a cron job for automatic backups:
(crontab -l 2>/dev/null; echo "0 3 * * * ~/backup-docker.sh") | crontab -
This sets up a daily backup at 3 AM.
Restore Procedures
To restore from a backup:
tar -xzf /path/to/backup/docker_backup_YYYY-MM-DD.tar.gz -C /
cd ~/docker_data
for d in */; do
cd "$d"
docker compose up -d
cd ..
done
WSL Integration
To access your self-hosted services from WSL:
Step 1: Install WSL on Windows
Open PowerShell as Administrator and run:
wsl --install
Restart your computer if prompted.
Step 2: Set Up Docker Desktop for Windows
- Download and install Docker Desktop for Windows from the official Docker website
- Enable the WSL 2 backend in Docker Desktop settings
- Enable integration with your WSL distribution in Docker Desktop settings[^4][^6]
Step 3: Access your self-hosted services from WSL
WSL can access your host services using the host’s IP address or hostname.
Add entries to your WSL hosts file:
sudo nano /etc/hosts
Add entries for your services:
192.168.1.x traefik.home.local portainer.home.local pihole.home.local
Replace 192.168.1.x with your actual server IP address.
Common Problems and Solutions
Problem: Docker container won’t start
Diagnostic:
docker logs container_name
Solution: Check for port conflicts, missing volumes, or configuration errors.
Problem: Cannot access services through Traefik
Diagnostic:
docker logs traefik
Solution:
- Verify Traefik is running:
docker ps | grep traefik
- Check labels in docker-compose.yml for correct host rules
- Ensure DNS is properly configured to resolve your domain names
Problem: Pi-hole DNS resolution issues
Diagnostic:
docker logs pihole
nslookup example.com your-server-ip
Solution:
- Check if Pi-hole container is running:
docker ps | grep pihole
- Verify port 53 is not being used by another service:
sudo lsof -i :53
- If systemd-resolved is using port 53, disable it:
sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved
Problem: WSL cannot access host services
Solution:
- Verify host IP address is correct
- Test connection with:
ping your-server-ip
- Check Windows Firewall settings
Known Bugs and Workarounds
Docker DNS Resolution Issues
Some Docker containers may have DNS resolution problems.
Workaround: Create or modify /etc/docker/daemon.json
:
sudo nano /etc/docker/daemon.json
Add the following:
{
"dns": ["8.8.8.8", "8.8.4.4"]
}
Restart Docker:
sudo systemctl restart docker
Traefik Certificate Issues
If Traefik has trouble obtaining certificates:
Workaround: Use a staging environment first:
certificatesResolvers:
letsencrypt:
acme:
caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
Once confirmed working, remove the caServer line.
Maintenance Procedures
Updating Containers
Use Watchtower for automatic updates:
cd ~/docker_data
mkdir -p watchtower
cd watchtower
Create docker-compose.yml:
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
watchtower:
image: containrrr/watchtower
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_SCHEDULE=0 0 4 * * *
- TZ=America/New_York
networks:
default:
external: true
name: proxy
EOF
Start Watchtower:
docker compose up -d
Monitoring
To check container health and resource usage:
docker stats
For detailed logs:
docker logs -f container_name
Stopping and Removing Services
To stop a service:
cd ~/docker_data/service_name
docker compose down
To completely remove a service including its data:
cd ~/docker_data
docker compose -f service_name/docker-compose.yml down -v
rm -rf service_name
Warning: This permanently deletes the service’s data.
Conclusion
You now have a complete self-hosted environment with Docker on Ubuntu, featuring:
- Traefik as a reverse proxy handling SSL certificates and routing
- Portainer for easy container management
- Pi-hole for network-wide ad blocking and DNS
- Integration with WSL for access from Windows
- Security best practices implemented
- Backup and maintenance procedures established
This solid foundation allows you to add more self-hosted services as needed. Simply follow the pattern of creating service directories in ~/docker_data
with appropriate docker-compose.yml files.
Remember to check the official documentation for each service you add, as configuration options may change over time. Regularly backup your data and keep your system updated to ensure security and stability.
Happy self-hosting!
Leave a Reply