Building an (almost) fully self-hosted, sandboxed, agentic software factory
Constructing an (Almost) Fully Self-Hosted, Sandboxed, Agentic Software Factory
By Jake Saunders
I have successfully engineered an agentic software factory that is nearly entirely self-hosted and securely sandboxed.
The result? A single prompt can trigger a cascade of events: the agent creates a repository, writes the application code and accompanying tests, ensures the CI pipeline is green, provisions a Postgres database, and deploys the final product behind HTTPS—all without a single follow-up instruction from me.
The Verdict: LLMs are exciting again! Perhaps they always were, and I was simply
stuck in the trough of disillusionmentoverthinking it.
The specific app I wanted was a basic CRUD application. Rather than paying for a commercial app store version, I used Claude to "one-shot" my own. While impressive, the idea of granting an LLM root access to my primary machine in autonomous mode is a security nightmare.
The Challenge
How do I build a remote, agentic development environment that structurally contains the LLM instead of relying on trust? I wanted a system that could autonomously navigate the entire Software Development Life Cycle (SDLC):
- Researching optimal stacks and dependencies.
- Writing and testing code.
- Deploying to a production server.
- Managing databases and observability (o11y).
- Configuring domains with SSL.
The goal: run everything on my home server to avoid another cloud infrastructure bill. The only recurring cost is a Codex subscription.
The Hardware
I am utilizing two distinct machines to ensure isolation:
- The Legacy Workhorse: A 2014 dual-core i3. This has been my homelab for five years, valiantly hosting this blog and Docker containers (ranging from Pi-hole to a full Prometheus/Loki/Grafana stack). I cannot risk an LLM breaking this.
- The Sandbox: A 2021 10th-gen i7 with 32GB RAM, sourced from eBay. This is a clean slate dedicated to the agent.
The Technical Stack
The core orchestration is handled by Coolify. While inference and certain integrations (Tailscale, Telegram, DNS, ACME) remain external, the rest is local. I chose not to host inference locally due to hardware constraints; I'm happy to let OpenAI subsidize my research.
| Component | Purpose | Notes |
|---|---|---|
| Pi-hole | Local DNS | Manages DNS rules and blocks intrusive ads. |
| Hermes (WebUI) | Virtual Assistant | OpenClaw-style agent utilizing Codex for inference. |
| Firecrawl | Web Layer | Self-hosted scraping/translation for the agent. |
| Porkbun | Registrar | Domain management. |
| Let's Encrypt | Security | Automated SSL certificate issuance. |
Note: I could provide an Ansible script for this setup; feel free to open an issue on my GitHub if interested.
Reference Materials
- Coolify Setup: See my guide on Coolify-on-Hetzner.
- Dockerfiles: I use specific production Dockerfiles optimized for Coolify.
- Forgejo Skill: I developed a Hermes skill for the Forgejo CLI (requires an API key).
Networking & Security Guardrails
The primary security measure is physical: dedicated hardware. If Hermes executes rm -rf /, the only cost is a few hours of rebuild time.
To further harden the system, I have disabled external ingress on the new server. There is no port 443 forwarded from the router, which eliminates the "background radiation" of the internet (bots probing for /wp-admin).
The Connectivity Paradox
If there is no ingress, how do I access the apps or generate SSL certificates for URLs like https://cool-new-app.internal.jakeshomelab.me?
The Solution: Tailscale + Pi-hole
I use Tailscale with my older server acting as an exit node. When remote, my traffic routes through that server and Pi-hole. I've implemented dnsmasq rules in Pi-hole:
address=/internal.jakeshomelab.me/192.168.1.201
Now, any request for *.internal.jakeshomelab.me resolves to the new server, where Coolify's reverse proxy handles the traffic.
SSL Certificates via DNS-01
I want valid HTTPS without publicly associating my internal services with my home IP via A records. I achieved this using the DNS-01 challenge.
- Domain: Purchased via Porkbun.
- Integration: Porkbun API keys are added to the Coolify environment.
- Configuration: The Coolify Docker Compose is modified to use
legoand the Porkbun provider:
# Traefik/Coolify Configuration Snippet
- '--certificatesresolvers.letsencrypt.acme.dnschallenge=true'
- '--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=porkbun'
- '--log.level=INFO'
The Workflow:
When a new URL is registered, Traefik/Coolify uses the Porkbun API to create a TXT record at _acme-challenge.my-service.internal.jakeshomelab.me. Let's Encrypt validates this record and issues the certificate.
The result is a valid HTTPS URL reachable only within my tailnet, with no public A or AAAA records. While the hostname might appear in public transparency logs, the service remains invisible to the public web.
The Development Stack & MCPs
With the isolated environment and networking solved, I moved to the tooling. I opted for Forgejo over GitHub for two reasons:
- Isolation: Providing a GitHub token to the sandbox would compromise the security boundary.
- Scalability: GitHub's API and CI minute limits are too restrictive for a high-volume software factory.
The setup involves Forgejo and its associated runners, allowing the agent to manage the entire version control and CI process locally.