Complete guide for deploying the Oullin monitoring stack on an Ubuntu VPS (Hostinger or similar).
- Prerequisites
- Initial Server Setup
- Install Docker and Docker Compose
- Install Make
- Clone Your Repository
- Configure Environment Variables
- Set Up Docker Secrets
- Configure Firewall
- Deploy the Monitoring Stack
- Verify Monitoring Stack
- Access Grafana Remotely
- Production Considerations
- Generate Test Traffic
- VPS Troubleshooting
- Updating the Stack
- Installing Fail2ban
- Hostinger VPS with Ubuntu 20.04 or 22.04 (or similar VPS provider)
- SSH access to your VPS
- Domain name (optional, but recommended for SSL)
- At least 2GB RAM and 20GB storage
Connect to your VPS:
ssh root@your-vps-ipUpdate the system:
apt update && apt upgrade -yCreate a non-root user:
# Create user
adduser deployer
# Add to sudo group
usermod -aG sudo deployer
# Switch to new user
su - deployerInstall required packages:
sudo apt install -y apt-transport-https ca-certificates curl software-properties-commonAdd Docker's official GPG key:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpgAdd Docker 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/nullInstall Docker:
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-pluginAdd your user to the docker group:
sudo usermod -aG docker ${USER}Log out and back in, then verify:
docker --version
docker compose versionsudo apt install -y makecd ~
git clone https://github.com/yourusername/your-repo.git
cd your-repoCreate your .env file with production settings:
cat > .env << 'EOF'
# Database Configuration
POSTGRES_USER=your_db_user
POSTGRES_PASSWORD=your_strong_db_password
POSTGRES_DB=your_database_name
# Grafana Configuration (optional - defaults to "admin")
# Strongly recommended to set a secure password for production
GRAFANA_ADMIN_PASSWORD=your_very_strong_grafana_password
# Production Domain (optional, for SSL)
DOMAIN=your-domain.com
# Environment
ENVIRONMENT=production
EOFSecurity Notes:
- Use strong, unique passwords
- If
GRAFANA_ADMIN_PASSWORDis not set, it defaults to "admin" - strongly recommended to change for production - Never commit
.envto version control - Consider using a password manager
Avoid piping credentials through echo because the literal values end up in your shell history. Use one of the safer patterns below.
# Prompt won't echo characters and won't touch shell history
read -s -p "Enter database password: " DB_PASSWORD && echo
echo "$DB_PASSWORD" | docker secret create pg_password - 2>/dev/null || \
printf "%s" "$DB_PASSWORD" > secrets/pg_password
unset DB_PASSWORDRepeat the same pattern for usernames or other sensitive values you do not want stored on disk.
mkdir -p secrets
printf "your_db_user" > secrets/pg_username
printf "your_strong_db_password" > secrets/pg_password
printf "your_database_name" > secrets/pg_dbname
chmod 600 secrets/*Store these files somewhere secure (e.g., pass, 1Password CLI, sops) and only copy them onto the server when needed.
Set up UFW:
# Enable UFW
sudo ufw --force enable
# Allow SSH (IMPORTANT: Do this first!)
sudo ufw allow 22/tcp
# Allow HTTP and HTTPS (for Caddy)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Verify rules
sudo ufw statusDo NOT expose Prometheus (9090), Grafana (3000), or postgres_exporter (9187) ports!
# Start with production profile
make monitor-up-prod
# Or: docker compose --profile prod up -dVerify services:
docker compose psExpected containers:
oullin_prometheusoullin_grafanaoullin_postgres_exporteroullin_proxy_prodoullin_db
Check Prometheus targets:
curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | {job: .labels.job, health: .health}'All should show "health": "up".
From your local machine:
ssh -L 3000:localhost:3000 deployer@your-vps-ipThen open http://localhost:3000 in your browser.
Login:
- Username:
admin - Password: Value from
GRAFANA_ADMIN_PASSWORD
Schedule daily backups:
crontab -eAdd:
# Run daily at 2 AM
0 2 * * * cd /home/deployer/your-repo && make monitor-backup-prod >> /var/log/prometheus-backup.log 2>&1# Check disk usage
df -h
# Check Prometheus data size
docker exec oullin_prometheus du -sh /prometheussudo tee /etc/docker/daemon.json > /dev/null << 'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOF
sudo systemctl restart docker
make monitor-restart-prodIf you have a domain, configure Caddy for automatic HTTPS.
Edit infra/caddy/Caddyfile.prod:
your-domain.com {
reverse_proxy api:8080
log {
output file /var/log/caddy/access.log
}
}
# Admin API (internal only)
127.0.0.1:2019 {
admin {
metrics
}
}Caddy will automatically obtain Let's Encrypt certificates.
make monitor-traffic-prodWait a few minutes for data to appear in Grafana.
# View logs from monitoring services
make monitor-logs # Local: all services
make monitor-logs-prod # Production: all services
# Or view individual container logs
docker logs oullin_grafana
docker logs oullin_prometheus
# Check Docker daemon
sudo systemctl status docker# Verify Grafana is listening
docker exec oullin_grafana netstat -tlnp | grep 3000
# Check if port is already in use locally
lsof -i :3000# Check DNS resolution
docker exec oullin_prometheus nslookup oullin_proxy_prod
docker exec oullin_prometheus nslookup oullin_postgres_exporter
# Verify network
docker network inspect caddy_net oullin_net# Clean up Docker
docker system prune -a --volumes
# Rotate backups (keeps last 5)
make monitor-backup
# Clear old Prometheus data
docker exec oullin_prometheus rm -rf /prometheus/wal/*cd ~/your-repo
git pull origin main
make monitor-down-prod
make monitor-up-prodsudo apt install -y fail2ban
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
sudo fail2ban-client status sshd- ✅
GRAFANA_ADMIN_PASSWORDset in.env(recommended for production) - ✅ Firewall configured (UFW)
- ✅ Services bound to localhost
- ✅ SSH tunneling configured
- ✅ Backups scheduled (cron)
- ✅ Log rotation configured
- ✅ SSL/TLS enabled (if domain)
- ✅ Fail2ban installed
- ✅ All Prometheus targets UP
- ✅ Dashboards accessible
- ✅ Retention policies set
- ✅ Volumes backed up regularly
For monitoring-specific documentation, see README.md.