This post documents how I fully automated my VPS setup with Ansible, from Docker and Nginx to SSL, OAuth2, and GitHub Runners with security and reproducibility in mind.
- Ansible roles that are idempotent and modular let you rebuild the entire server from scratch in minutes.
- Layering OAuth2 Proxy in front of sensitive containers beats per-app authentication schemes.
- A single ansible-playbook command is a better interface than a runbook full of manual steps.
Infrastructure overview
Everything runs inside containers for isolation. The core stack:
- Docker — all services containerized
- Nginx — reverse proxy and TLS termination
- Certbot — automatic SSL certificates via Let's Encrypt
- OAuth2 Proxy + Keycloak — centralized authentication in front of sensitive apps
- Grafana + Prometheus + Node Exporter — monitoring stack
- GitHub Actions Runner — self-hosted CI
- UFW + Fail2Ban — firewall and brute-force protection
- Watchtower — automated Docker image updates
Why Ansible?
Using Ansible means:
- Everything is defined as code
- No configuration drift between re-provisions
- Re-deployments are predictable and safe to run repeatedly
All roles are modular and idempotent. A full server rebuild takes minutes.
Security-first setup
Sed quis custodiet ipsos custodes?
OAuth2 Proxy + Keycloak
Sensitive apps sit behind oauth2-proxy, authenticating through Keycloak. Each app config includes a simple OAuth toggle:
nginx_apps:
- domain: subdomain.smikic.com
port: 69420
oauth: trueThis generates a custom Nginx config with an embedded OAuth2 block. Only authenticated users reach the app.
Let's Encrypt + Certbot
SSL certificates are automatically requested and renewed using Certbot with the Nginx plugin. Ansible handles everything — only a domain and email are required.
Firewall rules via UFW
UFW is configured dynamically:
- Only required ports are open
- OAuth2-protected apps get UFW rules only if the toggle is enabled
- Node Exporter binds only to localhost
Fail2Ban is installed and enabled for SSH brute-force protection.
Monitoring with Grafana and Prometheus
Monitoring runs on a dedicated Docker network:
- Prometheus scrapes Node Exporter and itself
- Grafana visualizes metrics and authenticates through Keycloak SSO
- Node Exporter exposes host metrics with hardened volume mounts
- Watchtower keeps images up to date automatically
Self-hosted GitHub Actions runner
Ansible installs all dependencies, configures the runner, and registers it as a systemd service — so it survives reboots and re-provisions without any manual steps.
Deployment is a single command
Once inventory and vars are set, provisioning the whole VPS is:
ansible-playbook -i inventory site.ymlRe-running this command at any time is safe. Ansible only makes the changes that are needed.
What's next
- Automatic backup and restore routines for volumes and secrets
- Alertmanager integration with Prometheus for alerting
- Extended monitoring coverage
- Potentially exploring zero-trust networking

Data is my veggies — healthy, versatile, and sometimes hard to digest, but in the end, it always brings value.