Deploy Markdown services on a Hetzner VPS using Podman Quadlet.
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install -y podman
# Verify version (must be 4.4+)
podman version
# Allow user services to run after logout
loginctl enable-linger $USER
# Install pactown
pip install pactown
# Initialize with your domain
pactown quadlet init \
--domain yourdomain.com \
--email admin@yourdomain.com
systemctl --user daemon-reload
systemctl --user enable --now traefik.service
# Verify it's running
systemctl --user status traefik.service
# Create a markdown file
cat > my-docs.md << 'EOF'
# My Documentation
Welcome to my documentation site!
## Features
- Fast and lightweight
- Automatic HTTPS
- Zero maintenance
## API Reference
See our [API docs](/api) for details.
EOF
# Deploy it
pactown quadlet deploy my-docs.md \
--domain yourdomain.com \
--subdomain docs \
--tenant myproject \
--tls
# Access at: https://docs.yourdomain.com
pactown quadlet shell --domain yourdomain.com --tenant myproject
# In the shell:
pactown-quadlet> config subdomain api
pactown-quadlet> deploy ./api-docs.md
pactown-quadlet> list
pactown-quadlet> logs api-docs
# Start API server
pactown quadlet api --port 8800 --domain yourdomain.com &
# Deploy via API
curl -X POST http://localhost:8800/deploy/markdown \
-H "Content-Type: application/json" \
-d '{
"markdown_content": "# Hello World\n\nDeployed via API!",
"subdomain": "hello",
"domain": "yourdomain.com",
"tenant_id": "myproject",
"tls_enabled": true
}'
Deploy services for multiple tenants:
# Tenant 1
pactown quadlet deploy ./docs/tenant1.md \
--domain yourdomain.com \
--subdomain tenant1 \
--tenant tenant1
# Tenant 2
pactown quadlet deploy ./docs/tenant2.md \
--domain yourdomain.com \
--subdomain tenant2 \
--tenant tenant2
# List all services
pactown quadlet list --tenant tenant1
pactown quadlet list --tenant tenant2
After deployment:
~/.config/containers/systemd/
├── traefik.container # Reverse proxy
├── traefik-letsencrypt.volume # TLS certificates
├── tenant-myproject/
│ ├── docs.container
│ └── content/
│ └── docs.md
├── tenant-tenant1/
│ └── ...
└── tenant-tenant2/
└── ...
# List services
pactown quadlet list --tenant myproject
# View logs
pactown quadlet logs docs --tenant myproject --lines 100
# Restart service
systemctl --user restart docs.service
# Stop service
systemctl --user stop docs.service
# Remove service
systemctl --user disable --now docs.service
rm ~/.config/containers/systemd/tenant-myproject/docs.container
systemctl --user daemon-reload
Default limits (configurable):
Modify in the .container file:
PodmanArgs=--cpus=1.0 --memory=512M --memory-reservation=1G
# Service status
systemctl --user status docs.service
# Real-time logs
journalctl --user -u docs.service -f
# Container stats
podman stats
# All Quadlet services
systemctl --user list-units 'tenant-*'
# Backup all Quadlet configs
tar -czf quadlet-backup.tar.gz ~/.config/containers/systemd/
# Backup Let's Encrypt certs
podman volume export traefik-letsencrypt > letsencrypt-backup.tar
For 50+ tenants, consider migrating to Kubernetes:
# Export to Kubernetes YAML
podman generate kube myproject-docs > k8s-docs.yaml
# Use with K3s
kubectl apply -f k8s-docs.yaml
# Check detailed status
systemctl --user status docs.service -l
# Check container logs
podman logs myproject-docs
# Verify Quadlet generation
/usr/libexec/podman/quadlet --dryrun --user
# Check Traefik logs
journalctl --user -u traefik.service -f
# Verify DNS is pointing to VPS
dig +short docs.yourdomain.com
# Check what's using port 80/443
ss -tlnp | grep -E ':(80|443)'