Zero to Production: Part 1 - Escaping Managed Tiers & Provisioning Oracle Cloud
So lets start from the beginning, this is a story of how a simple local use case ballooned into a production ready setup. I wanted to build a ToDo list and Time Tracker app for my personal use. So I chose Next.js to run it so that it could be an offline-first Progressive Web App (PWA). For storage and APIs I relied heavily on the free managed tiers of Supabase and PowerSync. They were generous and frictionless. But as the application grew—and as I looked ahead toward a future of fully self-hosting these services on my own hardware—I realized it was time to escape the limits of these managed tiers.
I wanted to move everything to my own Virtual Private Server (VPS) so I could store more data, manage advanced functionalities, and essentially run a practice drill for my ultimate goal of a home-lab bare-metal setup.
So I started by exploring the wild west of VPS providers and tried to secure the most affordable one to kick start my journey.
The Hardware: Why Oracle Cloud Wins (and AWS/GCP Fails)
If you try to run a heavy database and sync engine on the standard 1GB RAM micro-instances from the AWS (t3.micro), GCP (e2-micro), or Azure (B1S) free tiers, they will instantly choke and crash. You technically could force them to boot by configuring a massive 4GB+ swap file, but the disk I/O bottleneck will heavily degrade backend performance and make local syncs feel sluggish.
The best option for this specific setup was the Oracle Cloud Infrastructure (OCI) Always Free Tier. For exactly $0 a month, this is what you get:
- Massive Compute: ARM-based Ampere A1 Compute instances offering up to 4 OCPUs and 24 GB of RAM.
- Ample Storage: 200 GB of NVMe Block Storage, providing plenty of disk I/O for Postgres databases and Docker volumes.
- Generous Bandwidth: 10 TB of outbound data transfer per month, easily handling any aggressive data request.
The Catch: Provisioning Hurdles
Oracle's generous specs come with a frustrating sign-up process. They employ aggressive fraud filters that frequently reject perfectly valid cards. More annoyingly, because the free ARM instances are highly sought after, you will often hit an "Out of Host Capacity" error when trying to spin up the VM.
The Real Solution: The "PAYG" Upgrade Trick
I started with the free account but immediately hit the capacity wall. Instead of relying on scripts to ping the API continuously for days hoping for a slot, I used the community-verified workaround: Upgrading to a Pay-As-You-Go (PAYG) account.
- Different Hardware Pools: Oracle prioritizes paying customers. When you upgrade to PAYG, your request for an instance bypasses the highly congested "Free Tier Only" queue and pulls from the vast, available commercial hardware pool.
- It Remains Free: Upgrading to PAYG does not cancel your Always Free benefits. As long as you explicitly select the "Always Free Eligible"
VM.Standard.A1.Flexshape and keep it under the 4 OCPU, 24GB RAM, and 200GB storage limits, your monthly bill stays at $0 (the UI might show an estimated cost, but this is just a quirk). - The Safety Net: In case you are still unsure, go to Billing -> Budgets in the Oracle console and set a hard alert for $1. If you accidentally provision a paid resource, you are notified immediately.
Provisioning the Server
It takes a few hours for the PAYG conversion to complete, and once my account was ready, I provisioned the server with the following specs:
- Image: Ubuntu 24.04 Minimal
- Shape: 4 OCPU, 24GB RAM, 200GB Disk
- Network: Created a VCN using the setup wizard with CIDR
10.0.0.0/24.
I generated the SSH keys, stored them securely, and deployed the server.
Securing the Foundation
Before installing anything, I SSH'd into the server to lock it down. Oracle Cloud relies on its Virtual Cloud Network (VCN) security lists to handle firewall rules at the network level. By default, Oracle's VCN acts as a strict hardware firewall and blocks all incoming ports. Because of this, setting up an OS-level firewall is technically a secondary layer of defense for this specific case.
However, implementing "defense in depth" is a standard best practice—and if you are following this using a different VPS provider (like DigitalOcean, Linode, or Hetzner), configuring the Uncomplicated Firewall (UFW) on the OS is absolutely mandatory.
# Explicitly allow SSH connections
sudo ufw allow OpenSSH
# Allow web traffic for Coolify and applications
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Enable the firewall
sudo ufw enableNext, I hardened the SSH daemon to prevent password-based brute force attacks:
sudo vi /etc/ssh/sshd_configI ensured the following lines were set:
PermitRootLogin prohibit-password
PasswordAuthentication no
X11Forwarding noThis ensures that login is only possible if you have the generated SSH keys. Finally, I installed fail2ban to automatically block malicious IPs trying to access the server because as soon as you expose an IP on the internet, botnets and other malicious actors will descend on it like a swarm.
The Control Plane: Why Coolify
To manage the deployments, I compared Dokploy and Coolify. While Dokploy is a fantastic tool, it is not fully open source, and ultimately I felt Coolify was the better control plane for this exact architecture. It acts like an open-source, self-hosted Vercel or Heroku, with some massive benefits for my stack:
- Supabase is a 1-Click Service: Coolify has a Supabase template natively integrated. You deploy the entire Postgres, GoTrue, Realtime, and Storage stack with a single click and edit environment variables right in the UI.
- Official PowerSync Support: The PowerSync team provides a pre-configured Docker Compose file designed to be deployed as a "Docker Compose Empty" resource inside Coolify.
- Zero-Config SSL & Routing: Supabase exposes multiple ports, and PowerSync needs its own. Coolify automatically configures a reverse proxy (Traefik) and generates Let's Encrypt SSL certificates for your subdomains without ever touching a config file.
- ARM64 Compatibility: Coolify, Supabase, and PowerSync all have native ARM64 Docker images, meaning they run flawlessly on the Oracle Ampere A1 architecture.
Installing Coolify
Installation is incredibly straightforward. On the server, I ran:
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bashTo access the Coolify dashboard initially, I had to open port 8000 in the Oracle Cloud network ingress rules. I accessed it via http://<ORACLE_PUBLIC_IP>:8000 (the public IP can be found from the Oracle Compute instance admin page), created my admin account, and set up a Coolify project workspace.
Routing and SSL: Making it Production-Ready
Accessing a control panel via an IP address and an exposed port isn't secure or sustainable. I wanted everything routed cleanly through my own domain.
Step 1: The Wildcard DNS Configuration
In my domain registrar's DNS settings (I am using Cloudflare), I created a wildcard A record to point all API traffic to the server.
- Type: A Record
- Name:
*(Resolves to*.<domain.com>) - Target:
<ORACLE_PUBLIC_IP> - Proxy Status: Orange (Proxied)
Keeping it proxied via Cloudflare hides the actual IP from the internet, which reduces IP exposure via DNS records.
Step 2: Informing Coolify
Back in the Coolify dashboard (Servers -> localhost -> General), I updated the Wildcard Domain field to: https://*.<domain.com>. This ensures that all services deployed via Coolify will be automatically routed via subdomains from your domain address.
Step 3: Securing the Dashboard
Finally, I moved the Coolify dashboard itself under my domain. In Coolify's main Settings -> General, I set the Instance Domain (FQDN) to: https://coolify.<domain.com>
As soon as I clicked save, Traefik intercepted the new domain rule, reached out to Let's Encrypt, and generated a live SSL certificate. After that I removed port 8000 from my Oracle Cloud ingress rules for security, and navigated to my brand new, fully secured URL. The hardware was locked in. The control plane was live.
Next up: Taming the elephant and deploying Supabase.