DEV Community

Cover image for Why One Happy Path Beats a Menu of Options When Your User Is an AI Agent
Dennison Bertram
Dennison Bertram

Posted on

Why One Happy Path Beats a Menu of Options When Your User Is an AI Agent

Give an engineer five ways to deploy an app and they'll pick one. When I gave an AI agent several ways, it tried a few, mixed them together, and left me debugging at midnight.

That's the reason agentic-hosting works the way it does. It's an open-source tool I built that turns a Linux server you own into a place AI coding agents can deploy apps and databases on their own. This post explains its main design rule, one way to do each job, and walks through the API calls an agent makes to go from a git repo to a live URL.

⭐ If this is useful, a star on GitHub helps other people find it.

🤖 Give this to your agent: point it at https://raw.githubusercontent.com/dennisonbertram/agentic-hosting/main/llms.txt (or the skill) and it can sign up and get going from that one link.

Why fewer options

I used to think "only one way to do it" was a sign of an unfinished product. Heroku, Kubernetes, Coolify, and Dokku all give you lots of settings, because people running servers have strong opinions about how to build and route things.

Agents don't have opinions. They make a guess, and if the guess fails they retry, sometimes with a different guess. Every extra option is another branch they can wander down that I never tested.

So agentic-hosting has one build system, one router, and one choice per database type. If there's only one path, the agent can't find a creative one I didn't plan for. Here's what that looks like in practice.

Building: git repo in, running app out

Apps are built with Nixpacks, a tool that looks at your code and figures out how to package it into a container without a Dockerfile. It's the only build option. You can't upload your own Dockerfile.

Deploying from git takes two calls. First the agent creates a service. The API requires an image field, so for git deploys it sends the placeholder "pending":

POST /v1/services
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "name": "my-app",
  "image": "pending",
  "port": 3000
}
Enter fullscreen mode Exit fullscreen mode

Then it starts a build:

POST /v1/services/{serviceID}/builds

{
  "source_type": "git",
  "source_url": "https://github.com/org/repo",
  "source_ref": "main"
}
Enter fullscreen mode Exit fullscreen mode

source_type has to be "git", and source_ref defaults to "main". Only HTTPS URLs from github.com, gitlab.com, bitbucket.org, sr.ht, and codeberg.org are accepted. Anything else gets rejected before the build starts.

While it builds, the agent can watch the logs:

GET /v1/services/{serviceID}/builds/{buildID}/logs?follow=true
Enter fullscreen mode Exit fullscreen mode

Because there's only one build path, the logs always look the same, and when something breaks there's one place to look. In my experience that helps the agent more than which build tool it is.

Routing: you get one URL

Traefik, an open-source proxy, takes incoming web traffic and sends it to the right app. agentic-hosting writes Traefik's config itself. Apps can't set their own routing rules, so a container can't quietly grab another app's traffic.

The URL follows one pattern:

  • By default: http://{serviceID}.localhost
  • If you start the server with --base-domain example.com (or set AH_BASE_DOMAIN): https://{dns_label}.example.com, where dns_label comes from the service name
GET /v1/services/{serviceID}
# → includes "url": "http://svc_….localhost"
#    or "https://my-app.example.com"
Enter fullscreen mode Exit fullscreen mode

Custom domains aren't supported yet. That's issue #82.

Databases: Postgres or Redis

The type field takes "postgres" or "redis", and nothing else.

POST /v1/databases

{ "name": "my-db", "type": "postgres" }
Enter fullscreen mode Exit fullscreen mode

It takes about 10 to 30 seconds. Then the agent grabs the connection string, adds it to the app's environment variables, and restarts the app:

GET /v1/databases/{dbID}/connection-string

POST /v1/services/{serviceID}/env
{ "DATABASE_URL": "<connection-string>" }

POST /v1/services/{serviceID}/restart
Enter fullscreen mode Exit fullscreen mode

Environment variables are encrypted on disk and hidden in API responses unless you add ?reveal=true.

The smaller limits

The same thinking shows up in small places. Creating a service accepts name, image, port, and optional env, so the agent can't request more memory or CPU. Images come from Docker Hub or the server's own local registry. And if the agent retries a request with the same Idempotency-Key header within 10 minutes, it won't deploy twice.

None of this limits what you can build. It limits how many ways the agent can get there, which is what I wanted.

Try it

With the server running (see the README, or run ./bin/ah --dev --open-registration --port 8080 on your laptop), the agent's path is:

  1. Register to get an API key.
  2. Create a service with "image": "pending".
  3. Start a build from a git URL.
  4. Watch the build logs, then the app logs at GET /v1/services/{id}/logs?follow=true&tail=100.
  5. Create a Postgres or Redis database, set the env var, restart.
  6. Open the service's url.

If you use Claude Code, the repo also has a skill and /ah-deploy, /ah-db, /ah-logs, and /ah-status commands that do the same steps.

Wrapping up

agentic-hosting gives an AI agent exactly one way to build, route, and add a database, which makes its runs a lot more predictable. You run it on your own server, and the agent does the deploying.

And if you hand your agent the keys and something breaks, tell me what it was.

Top comments (0)