36 Lessons From Self-Hosting an Automation Project End to End
Most tutorials show you the happy path. Sign up here, click this, paste that, done. What they rarely show you is the host that won't resolve, the model that got deprecated mid-build, or the bug that returns a string instead of the file you asked for.
I kept notes on all of it while building a self-hosted expense tracker, a Telegram bot backed by n8n, Gemini, and a free Postgres database. What follows is the condensed version, 36 things worth knowing before you hit them yourself.
Account and Service Setup
The boring part, but worth doing right the first time.
- ngrok — sign up free, no card. Grab the permanent dev domain and authtoken from the dashboard immediately, both stay valid indefinitely.
- Telegram bot — message @BotFather, send
/newbot, get a token. Tokens don't expire unless you manually revoke them. - Google AI Studio (Gemini key) — free API key, no card. Ignore the "Set up billing" prompt, it's optional.
- Supabase — sign up free, GitHub or email works the same either way. Create an org, then a project, and save the database password immediately, since it's shown to you exactly once.
Local Infrastructure
This is where "free" starts to mean actual infrastructure decisions, not just a signup form.
- Install Docker Desktop, then enable "Start Docker Desktop when you sign in" under Settings → General.
- Write a
docker-compose.ymlwithrestart: unless-stopped, so containers heal themselves after a crash or a full reboot. docker compose up -dto start,docker compose downto stop, anddocker compose down && docker compose up -dany time you need a config change to actually apply.ngrok http --url=yourdomain.ngrok-free.dev PORTtunnels a local port to a stable public URL.- On Windows, auto-start ngrok through a
.batfile dropped into the Startup folder (Win+Rthenshell:startup).
Database Setup, the Supabase-Specific Parts
The parts that a generic Postgres tutorial won't mention, because they're specific to how Supabase routes connections.
- Create the project, run your schema once through the SQL Editor. Don't hand-create tables through the UI, it doesn't scale past the first table.
- Get connection details through the green Connect button, not a buried Settings page.
- If you hit "host not found," it's because Supabase's direct connection is IPv6-only. Switch to the Session Pooler connection on port 5432 instead, which supports IPv4.
- If you then hit "self-signed certificate in certificate chain," set the credential's SSL mode to Allow and enable Ignore SSL Issues. Still encrypted, just less strict about the certificate chain, a known and safe workaround for a personal project.
- Session pooler usernames are formatted
postgres.<project-ref>, not justpostgres. Copy this straight from the Connect panel rather than assuming the format.
Building the Actual n8n Workflow
Where most of the real debugging time went.
- Import workflows through Create workflow → Start from scratch → the "..." menu → Import from file, not from the main Workflows list dropdown, which doesn't have that option.
- Every node showing a red warning triangle needs a credential assigned. Click in, pick from the dropdown, save.
- For APIs authenticated via a URL query parameter, like Gemini's
?key=, skip the saved-credential system entirely and paste the key directly into the node's URL field. - Publish after every single node edit. Saving a node is not the same as the live, webhook-triggered workflow actually using that change.
- Use Execute step on a scheduled workflow to test it immediately, instead of waiting for the real schedule to fire.
- Branching nodes need an explicit fallback output enabled, or any input that doesn't match a defined rule silently goes nowhere, no error, nothing.
- Check the Executions tab after any failure. It shows exactly which node failed and the real error message, reading it beats guessing every time.
AI Integration Gotchas
The part that felt most like working with a living dependency rather than a fixed tool.
- Don't treat a specific model name as permanent infrastructure. Models get deprecated with little warning, I hit this mid-build when a model's free quota dropped to zero overnight.
- For structured extraction, spell everything out explicitly: exact JSON keys, the exact allowed values, and "no prose, no markdown fences, JSON only."
- For binary file handling, like converting a downloaded image to base64, use a platform's dedicated built-in node instead of hand-rolled code. A Code node's binary handling can have real, hard-to-diagnose sandbox bugs.
A Debugging Method Worth Repeating
Two habits that saved the most time, across every kind of failure.
- When an API call fails, read the full error detail panel, not just the one-line summary. The actual request body sent usually reveals the real problem in seconds.
- Isolate failures node by node using "Execute step," rather than re-running the whole chain and guessing from the end result.
Cost and Security Discipline
Non-negotiable if the goal is genuinely $0 with nothing sensitive exposed.
- Free tiers that require a card for "verification" are avoidable. Local hosting plus a free tunnel service covers the same need with zero card risk.
- Never let real credentials end up in exported files. Keep placeholders in anything you save or share, so a backup is never also a leak.
- Before pushing to a public repo, grep every file for key patterns, hostnames, and IDs. Don't assume it's clean just because it looks clean.
- Add a
.gitignorebefore you think you need one. It's a cheap safety net that only works if it's in place before the risky file ever gets created.
Reliability and Production Readiness
The difference between a demo and something you'd actually trust to run unattended.
restart: unless-stoppedin Docker Compose, so containers recover automatically.- Enable Docker's own auto-start on login.
- Auto-launch any companion process, like a tunnel, through the OS's own startup mechanism.
- Actually test durability with a real full restart. Don't assume auto-start settings work until you've watched them work.
Data Integrity, Learned Late
The two lessons that showed up only after everything else already looked finished.
- AI-based input parsing needs an explicit validity check, like a boolean flag returned from the model, or it will confidently generate structured garbage from meaningless input, and your pipeline will happily save it as if it were real.
- Keep "what the AI is allowed to output" and "what the system tracks against" documented as a pair. They will quietly drift apart otherwise, and neither half of that drift throws an error.
The Actual Takeaway
None of these are exotic. Each one is a small, specific thing that cost real time the first time it happened and costs nothing the second time, because now it's just a checklist. That's the whole case for writing this down instead of trusting memory for the next project.