Building a Secure Private Cloud in 2026: Coolify, Directus & Cloudflare Tunnels
How I stopped opening ports on my router and built a three-layer fortress for my home server — no VPN, no exposed IP, no compromise.
The Problem: The "Home Server" Headache
We've all been there.
You've spent a Sunday afternoon setting up a beautiful self-hosted service on your local machine — maybe a headless CMS like Directus, a media server like Jellyfin, or a custom internal tool you've been hacking on for weeks. It works perfectly on your home network. Then you step out to a café, pull out your phone, and the illusion shatters. You can't access it. You're locked out of your own creation.
The traditional solutions are uncomfortable at best and dangerous at worst:
- Port forwarding means punching holes in your router's firewall, directly exposing your home IP address to the public internet. One misconfigured service and you have a serious problem.
- CGNAT (Carrier-Grade NAT) is increasingly common with ISPs and makes traditional port forwarding outright impossible for many users. Your router isn't even the outermost layer anymore — your ISP is.
- Dynamic DNS solves the changing-IP problem but does nothing for the security issues above.
- A VPN like WireGuard is a solid approach, but it requires you to remember to activate it on every device, every time. It's friction, and friction leads to bad habits.
There had to be a better way. In 2026, there is — and this post walks through exactly how I built it.
The Philosophy: A Three-Layer Fortress
Before diving into the technical steps, it's worth understanding the design philosophy behind this stack. Instead of reacting to security threats, we're building a system that is secure by default — where every layer reinforces the next.
Think of it like this:
- The Orchestrator manages and deploys your services cleanly, in isolated containers.
- The Application is your actual service, running privately with no public exposure.
- The Bridge creates an outbound-only, encrypted tunnel to the internet — so traffic reaches you without anyone ever knowing where "you" are.
This is the modern private cloud stack. Here's what each layer looks like in practice.
The Stack
1. 🧩 Coolify — The Orchestrator
Coolify is a self-hosted Platform-as-a-Service (PaaS) that brings the developer experience of platforms like Heroku or Railway to your own hardware. It wraps Docker and Docker Compose in a clean web UI, so you can deploy, manage, monitor, and update containerised applications with a few clicks — no memorising docker-compose flags required.
Why Coolify instead of managing Docker directly?
- Visual dashboard for all running services
- Built-in environment variable management
- One-click deployments and redeployments
- Automatic container health monitoring
- Log streaming directly in the browser
For anyone running more than one or two services on a home server, Coolify is transformative. It turns container orchestration from a chore into a pleasant experience.
2. 🗄️ Directus — The Data Engine
Directus is an open-source headless CMS and data platform. Unlike traditional CMS platforms that dictate your content structure, Directus wraps around any SQL database and instantly generates a REST and GraphQL API. It's database-first, meaning your data is never locked in.
Why Directus?
- Flexible, schema-agnostic data modelling
- Auto-generated REST & GraphQL APIs
- A beautiful, intuitive admin UI
- Role-based access control out of the box
- Works seamlessly with PostgreSQL, MySQL, SQLite, and more
For this project, Directus serves as the content backend — the place where all data lives and is managed. It's the thing we ultimately want to access securely from anywhere in the world.
3. 🌐 Cloudflare Tunnels — The Bridge
This is the secret sauce. Cloudflare Tunnels (formerly Cloudflare Argo Tunnel) allows you to expose a locally running service to the internet without opening a single port on your router.
Here's the magic: instead of the internet coming to you, your server reaches out to Cloudflare. The cloudflared daemon running on your machine establishes a persistent, encrypted, outbound-only connection to Cloudflare's global edge network. When a user visits your domain, Cloudflare routes the request through that tunnel to your local machine — without ever knowing (or exposing) your real IP address.
Why Cloudflare Tunnels?
- Zero inbound ports required — works even behind CGNAT
- Your home IP is completely hidden from the public
- Free SSL/TLS certificates, automatically managed
- Integrates natively with Cloudflare's Zero Trust access controls
- Runs as a lightweight, containerised daemon
Step-by-Step: Building the Stack
Phase 1 — Deploying Directus with Coolify
The first phase is getting Directus up and running locally, managed by Coolify.
Installing Coolify
Coolify is designed to run on a fresh Linux server or even a local machine. Installation is a single command:
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
Once running, access the Coolify dashboard at http://your-server-ip:8000 and complete the initial setup. From here, everything is managed through the UI.
Deploying Directus via Docker Compose
In Coolify, create a new service and select Docker Compose as the deployment method. Use a compose file similar to the following:
version: "3"
services:
database:
image: postgres:15-alpine
environment:
POSTGRES_USER: directus
POSTGRES_PASSWORD: supersecretpassword
POSTGRES_DB: directus
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U directus"]
interval: 10s
timeout: 5s
retries: 5
directus:
image: directus/directus:latest
depends_on:
database:
condition: service_healthy
ports:
- "8055:8055"
environment:
KEY: "your-random-key-here"
SECRET: "your-random-secret-here"
DB_CLIENT: "pg"
DB_HOST: "database"
DB_PORT: "5432"
DB_DATABASE: "directus"
DB_USER: "directus"
DB_PASSWORD: "supersecretpassword"
ADMIN_EMAIL: "admin@example.com"
ADMIN_PASSWORD: "your-admin-password"
PUBLIC_URL: "https://cms.yourdomain.com" # ← Critical!
volumes:
- directus_uploads:/directus/uploads
volumes:
postgres_data:
directus_uploads:
⚠️ The Critical PUBLIC_URL Setting
The single most important environment variable here is PUBLIC_URL. Directus uses this value to construct absolute URLs for things like password reset emails, file previews, and the Admin UI's API calls.
If PUBLIC_URL is set to http://localhost:8055 but you access Directus via https://cms.yourdomain.com, the admin panel will make API calls to the wrong address and break in subtle, confusing ways. Set this to your final public domain from the start. In our case, that will be the domain we configure through Cloudflare.
Once deployed, Coolify will show Directus as healthy and running — but it's only accessible on your local network for now. That changes in Phase 2.
Phase 2 — Building the Cloudflare Tunnel
This is where things get elegant. We're going to create a secure bridge between Cloudflare's global network and our locally running Directus instance.
Prerequisites
- A domain name added to Cloudflare (with Cloudflare managing DNS)
- A free Cloudflare account with Zero Trust enabled
Creating the Tunnel
- Navigate to Cloudflare Dashboard → Zero Trust → Networks → Tunnels
- Click Create a tunnel and give it a name (e.g.,
home-server) - Cloudflare will generate a unique Tunnel Token — copy this, you'll need it shortly
- Under Public Hostnames, add a new route:
-
Subdomain:
cms -
Domain:
yourdomain.com -
Service:
http://directus:8055(the internal Docker service name and port)
-
Subdomain:
Running cloudflared as a Container
Rather than installing the cloudflared binary directly on the host, we run it as a Docker container — keeping things clean and managed by Coolify. Add the following to your Docker Compose file (or deploy it as a separate Coolify service):
cloudflared:
image: cloudflare/cloudflared:latest
command: tunnel --no-autoupdate run
environment:
TUNNEL_TOKEN: "your-tunnel-token-from-cloudflare"
restart: unless-stopped
depends_on:
- directus
Once this container starts, it establishes an outbound connection to Cloudflare. Within a few seconds, cms.yourdomain.com will resolve publicly — routing through Cloudflare's edge directly to your Directus container.
No port forwarding. No firewall rules. No exposed IP address.
Your Directus instance is now live on the internet. But before you celebrate, we need to lock it down.
Phase 3 — The Zero Trust Lockdown
Having a publicly accessible URL is both exciting and frightening. Anyone with the URL can reach your Directus login screen. While Directus itself has authentication, relying solely on an application-level login is a single point of failure. We want an additional layer — one that stops uninvited guests before they even see Directus.
Enter Cloudflare Access.
What is Cloudflare Access?
Cloudflare Access is part of Cloudflare's Zero Trust product suite. It acts as an identity-aware proxy that sits in front of your application. Before a request reaches Directus, Cloudflare intercepts it and demands authentication. Only requests that pass the policy are forwarded through the tunnel.
Setting Up an Access Policy
- Go to Zero Trust → Access → Applications
- Click Add an application → Self-hosted
- Configure it:
- Application name: Directus CMS
-
Application domain:
cms.yourdomain.com
- Create an Access Policy:
- Policy name: Owner Access
- Action: Allow
-
Include rule:
Emails→your@email.com
How the OTP Login Works
When a user navigates to cms.yourdomain.com, here's what happens:
- Cloudflare intercepts the request before it reaches Directus
- The user is presented with a Cloudflare Access login screen
- They enter their email address
- If the email matches an allowed email in your policy, Cloudflare sends a One-Time PIN (OTP) to that address
- The user enters the PIN, receives a signed JWT session cookie, and is forwarded to Directus
- All subsequent requests carry this cookie, so Directus loads normally
The result: even if someone discovers your cms.yourdomain.com URL, they cannot proceed without access to your email inbox. The Directus login screen is invisible to the outside world.
Why This Stack Wins
Let's step back and appreciate what we've built.
✅ No VPN Required
Traditional secure remote access requires a VPN client on every device. You have to remember to connect, manage certificates, and troubleshoot when it doesn't work. With Cloudflare Tunnels + Access, any browser on any device can securely access your service. Your phone, a borrowed laptop, a friend's computer — all work instantly with just an email and an OTP.
✅ Free SSL/TLS, Automatically Managed
Cloudflare handles HTTPS termination at the edge. You get a valid, trusted SSL certificate for your subdomain at no cost, with automatic renewal. There's no Certbot, no Let's Encrypt configuration, no annual renewal panic.
✅ Your Home IP is Completely Hidden
Because cloudflared only makes outbound connections, there is no DNS record pointing to your home IP, no open port for scanners to find, and no way for a malicious actor to correlate your domain with your physical location or ISP. From the public internet's perspective, your service lives somewhere inside Cloudflare's network.
✅ Layered Security by Design
Public Internet
│
▼
Cloudflare Edge (DDoS protection, WAF)
│
▼
Cloudflare Access (Identity verification, OTP)
│
▼
Cloudflare Tunnel (Encrypted, outbound-only)
│
▼
Directus Application (App-level authentication)
│
▼
PostgreSQL (Internal network only, never exposed)
Each layer must be bypassed independently. This is defence-in-depth done properly.
✅ Scales With You
This exact pattern works whether you're running one service or twenty. Each new service gets its own subdomain, its own tunnel route, and its own Access policy. Coolify handles the container orchestration; Cloudflare handles the networking and access control. The complexity doesn't grow with the number of services.
Common Gotchas & Tips
Directus Admin UI breaking after external access?
Almost always a PUBLIC_URL misconfiguration. Double-check it matches your Cloudflare hostname exactly, including https://.
Cloudflared container keeps restarting?
Verify your TUNNEL_TOKEN is correct and that the tunnel is in an active state in the Cloudflare dashboard.
OTP emails not arriving?
Check your spam folder first. If using a custom domain email, ensure your SPF/DKIM records are correctly configured with Cloudflare.
Want to allow a team member access?
Simply add their email to your Access Policy's include rule. They'll be able to authenticate with their own OTP without needing any VPN credentials or shared passwords.
Conclusion: Welcome to the Private Cloud
The era of opening Port 80 on your home router and crossing your fingers is over. Modern tools — Coolify for orchestration, Directus for your data layer, and Cloudflare Tunnels with Zero Trust Access for networking — give individual developers and small teams the kind of infrastructure that used to require a dedicated DevOps engineer and a cloud budget.
The stack we've built today is:
- Accessible — from any device, anywhere in the world, with just a browser
- Secure — hidden IP, layered authentication, encrypted transit
- Free (or nearly so) — Coolify is open-source, Directus has a generous self-hosted licence, and Cloudflare's free tier covers everything we've used here
- Maintainable — clean abstractions at every layer, easy to update and extend
If you're still running services with a raw docker run command and a prayer, this is your sign to upgrade. The private cloud is here, and it's better than ever.
Built and tested on a home server running Ubuntu 24.04 LTS. Stack versions: Coolify v4, Directus 11, cloudflared 2025.x.
Top comments (0)