Operating my own application infrastructure
Self-Hosted Application Platform & Migration
Built and operate a self-hosted application platform on Ubuntu using Docker, Coolify, Forgejo, Cloudflare, Infisical, PostgreSQL and Redis, migrating real applications from GitHub/Vercel-managed workflows to infrastructure I operate directly.
- Ubuntu
- Docker
- Coolify
- Forgejo
- Cloudflare
- Infisical
- PostgreSQL
- Redis
- UFW
- Fail2ban
- Restic
Context
I moved beyond fully managed hosting to operate the infrastructure behind real applications myself. This meant taking responsibility for Git hosting, deployments, container networking, databases, secure access, backups and recovery rather than relying on those layers being abstracted by managed platforms.
Architecture
Compute
- Ubuntu VPS
- Docker
- Coolify
Source & deployment
- Forgejo
- SSH deploy keys
- Git-based deployments
Data
- PostgreSQL
- Redis
- Persistent Docker volumes
Secrets
- Self-hosted Infisical
Edge
- Cloudflare DNS
- Cloudflare proxy
- Cloudflare Tunnel
Recovery
- Restic
- Cloudflare R2
- PostgreSQL dumps
- systemd automation
Security
Implemented SSH key authentication, restricted root/password login, UFW, Fail2ban, provider-edge firewalling and /32 source-IP allowlisting.
I also separated professional and private/business Git infrastructure using independent Forgejo instances, SSH identities, ports and deployment credentials.
Deployment
I configured repository authentication, deploy keys, production branch alignment and push-based deployment automation rather than treating the deployment platform as a black box.
- Git push
- Forgejo
- Coolify
- Container build & deployment
- Cloudflare
- Production
Backup & recovery
Implemented encrypted off-site Restic backups to Cloudflare R2, PostgreSQL logical dumps, archive validation and automated backup, integrity-check and retention jobs using systemd.
A controlled end-to-end restore test is the next recovery milestone.
Real troubleshooting
Operating the platform has included diagnosing:
- SSH lockout: traced an SSH hang to a changed public IP no longer matching a provider-firewall /32 allowlist.
- Forgejo collision: separated two Forgejo instances using host SSH ports 22222 and 22223.
- Failed Coolify deployment: traced git upload-pack: not our ref to Coolify connecting to the wrong Forgejo and corrected the custom-port Git source using an explicit ssh:// URI.
- Branch/deployment mismatch: verified Git ancestry before removing an obsolete master branch and standardising production on main.
What I learned
Self-hosting moved my understanding beyond simply deploying applications. I now work across the full delivery path — Git, authentication, builds, containers, networking, reverse proxying, DNS, persistent state, security and recovery — and troubleshoot failures by isolating the affected layer before making changes.
Next
Restic restore testing, Infisical backup and secrets integration, CrowdSec, attack-surface review, monitoring/alerting, Renovate/dependency security automation and a complete disaster-recovery runbook.