Raw capture ā raw/building-an-almost-fully-self-hosted-sandboxed-agentic-software-factory.md
url: https://blog.jakesaunders.dev/building-an-almost-fully-self-hosted-sandboxed-agentic-software-factory/ date_fetched: 2026-09-04
tl;dr: It worked! From one prompt it created a repo, wrote the application and tests, got CI green, provisioned Postgres and deployed the finished app behind HTTPS without another message from me.If you just wanna see the outcome you can find a demo video at the bottom
LLMs got fun again! Maybe they always were and I was just stuck in the trough of disillusionment. Lately, whenever I need a little tool, I just build it.
I was in the gym the other day and wanted a weights tracker. The app I had in mind was about as CRUD-y as it gets, but all the app store versions wanted Ā£12 per month, so I just one-shotted one with Claude. Great fun, but giving an LLM root access to my machine in auto mode still doesnāt sit right with me.
So, the challenge: how can I create a fully remote agentic development environment where we structurally contain the LLM rather than just trusting it? I want to give it an instruction and have it autonomously move through the whole SDLC:
All on my home server, without another cloud infrastructure bill. The only ongoing cost specific to this experiment is a £20 Codex sub.
Here they are in all their glory.
The one at the bottom is a 2014 dual-core i3 Iāve been running as a homelab for five years. Itās valiantly hosting this blog and about 45 other Docker containers, from Pi-hole to a full Prometheus / Loki / Grafana stack. It also has port 443 forwarded from my router. Iād be miffed if an LLM broke it, so thatās not what weāre using today.
The top one is a 2021 10th-gen i7 with 32GB RAM, bought fresh from eBay with nothing on it. Perfect.
The core development stack is self-hosted through Coolify. Inference and integrations like Tailscale, Telegram, DNS and ACME still leave the box. You could host inference too, but I donāt have the hardware and Iād rather OpenAI subsidise my experiments.
| Component | Notes |
|---|---|
| Pi-hole | Local DNS rules, with the side benefit of seeing less shitty advertising. |
| Tailscale | Makes my home network follow me around. |
| Coolify | A self-hosted, Heroku-style PaaS built on Docker. |
| Forgejo (with runners) | Self-hosted Git and CI. |
| Hermes (with WebUI) | An OpenClaw-style virtual assistant, using Codex for inference. |
| Telegram | Talk to the agent from the toilet or wherever. |
| Firecrawl (self-hosted) | A scraping / translation layer between the agent and the web. |
| Porkbun (Registrar) & Letās Encrypt | A domain and on-the-fly SSL certificates. |
| Whatever else | Postgres, Redis, whatever your apps need. Itās just Docker under the hood, innit? |
This isnāt a full how-to guide. I could probably write an Ansible one-shot script to set it all up; leave an issue on the GitHub repo below if youād like one. If youāve read this far, though, you can probably figure it out.
| Source | Notes |
|---|---|
| Coolify Setup Blog | My Coolify-on-Hetzner setup guide. |
| Coolify Dockerfiles | Production Dockerfiles that actually work on Coolify. Just add env vars. |
| Forgejo Hermes Skill | A Hermes skill for the Forgejo CLI; just provide a key. |
The first guardrail is obvious: itās on its own metal. Hermes could rm -rf / and at worst it would cost me a couple
of hours rebuilding it.
The next layer of bombproofing is the network. My older server has port 443 forwarded from the router; this one doesnāt.
Thereās no external ingress, cutting out a huge attack surface and all the internet background radiation from people
speculatively probing /wp-admin on every DNS A record I set up.
But, if thereās no ingress, how do I:
https://cool-new-app.internal.jakeshomelab.me?I have Tailscale set up with my older server as an exit node. When Iām away from home, selecting it routes my traffic through that server and Pi-hole, which Iām using for custom DNS. Pi-hole lets you add dnsmasq rules like this:
address=/internal.jakeshomelab.me/192.168.1.201
Anything requesting *.internal.jakeshomelab.me now resolves to my new server, where Coolifyās reverse proxy picks it
up and serves my shiny new services.
With Caddy or Traefik and Docker labels, you can serve port 3000 on container X from
https://my-service.internal.jakeshomelab.me. Point an A record at the server and itāll contact Letās Encrypt,
complete an ACME challenge and get an SSL cert. I learned this three years ago and it still seems like magic.
The problem is the A record. I donāt want to publicly associate my-service.internal.jakeshomelab.me with my IP,
whether people can access it or not. I want an SSL cert for a ghost service.
To solve this problem, I turned to DNS-01. Iāll be honest this is new to me, but hereās how it works:
legoand the Porkbun API: - '--certificatesresolvers.letsencrypt.acme.dnschallenge=true'
- '--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=porkbun'
- '--log.level=INFO'
Then, when I register a new URL, Traefik / Coolify:
_acme-challenge.my-service.internal.jakeshomelab.me.Thatās it! You now have a valid HTTPS URL, reachable within your tailnet, with no public A or AAAA record pointing to the service. The hostname may still appear in public certificate-transparency logs, but the service is only reachable from the tailnet.
The best bit is that Coolify does this on the fly. Our agent can create a service at any subdomain and itāll āØmagically⨠sort itself out.
So, glue all this together and you get the following:
The same setup covers the tooling, so Coolify, Hermes, Forgejo and Firecrawl all live on their own local subdomains.
Now we have an isolated(ish) box, letās move on to the tooling. The tools are well known; gluing them together is the fun part.
We need somewhere durable to store code and run CI. I decided not to use GitHub because:
Forgejo is a great self-hosted alternative. The Docker Compose file linked above sets up Forgejo and its runners; registering yourself and the runner takes a little extra work, but itās well documented.
Iāve also included a Compose file for syncing projects back to GitHub. That puts your GH token in the environment, but the trade-off is yours to make.
The Forgejo Hermes skill linked above gives the agent full control of the instance.
Hermes is an OpenClaw-style personal assistant with agentic capabilities. I never got in on the OpenClaw hype, so I canāt compare the two, but Hermes has a few features Iāve found handy:
Getting Hermes and Firecrawl set up with the right keys in the right places is a massive pain in the arse. Iāve added Coolify-friendly Docker Compose files to the repo linked above.
Coolify is the glue holding this together: a self-hosted PaaS built on Docker and Compose that comes on in leaps and bounds with every update. If you want Heroku or DigitalOcean App Platform niceties on your own hardware, Iād highly recommend it.
Some of my favourite features are:
docker exec <whatever>from your laptop. Things are only abstracted away if you want them to be.Here are a couple of screenshots of my Coolify setup in action:
The demo below shows this pretty well, but the starting gun was the following prompt:
Please build me an app for tracking my calorie intake. It should be similar to MyFitnessPal but with a form to
add specific food and meals for quick selection later.
Your task is to build it, commit it to a new repo with tests, test it with CI, and deploy it to
http://calories.internal.jakeshomelab.me.
Iād like it to be a full stack svelte kit app with Drizzle and Postgres for the database layer.
Iād like tailwind for the CSS. It should be mobile first.
For deployment, please use docker and docker compose and deploy your own Postgres instance.
From there, it just got on with it:
All without a single further prompt. No nudging it through failed tests or copying error messages back into the chat. It just kept going until the app was running.
At that point I gave it a whirl and hit a CSRF issue when submitting data. I sent one more prompt; it diagnosed the problem, fixed it, added regression tests and redeployed.
And it worked!
Thatās the loop I wanted: prompt, repo, code, tests, CI, deployment, bug fix. Itās not a complicated app, obviously, but it went from a paragraph to tested, deployed software and handled all the boring bits in between. That still feels a bit like witchcraft.
Iām no YouTuber, but here you go:
There is always a trade-off between fully agentic development and security. This was a fairly contrived example: the app works completely in isolation. Most useful software talks to other software, which means handing over API keys, and every key adds another little hole in the sandbox.
Even in this setup, Hermes can still:
So no, it isnāt harmless. What Iāve done is make the machine sacrificial and sharply limit how much stuff I care about is within reach. The failure mode is now ārebuild the eBay box and rotate a handful of keysā, rather than ādiscover an LLM has enthusiastically reorganised my actual laptopā. Thatās better I think, but it isnāt magic.
The obvious next steps are:
Automate backups and make rebuilding the whole box a one-shot job.- Coolifyās DB backup and my shared Docker compose mounts should make this relatively easy.
Require approval before it does anything genuinely public or difficult to undo.
At some point, though, enough approval gates turn your magical autonomous software factory back into a collection of forms you have to fill in. Finding the useful point between āneeds me every five minutesā and āhas the launch codesā is the next experiment.