ai newsDo., 29. Okt.
EilmeldungParis-based Mistral releases "Le Chonk," a strong coding model that still trails China overall

I Let My AI Agent Create Its Own Databases. Here's How the Passwords Stay Locked Up.

There's a moment in almost every project when the app needs to remember something. My agent has written the code and shipped it, and now it needs a database. For a long time, that was where I took over. I'd click around a dashboard, copy a password, paste it into a .env file, and then paste it into

dev.toDonnerstag 15:29 · 29.10.9 Min. Lesezeit
Bild: dev.to

There's a moment in almost every project when the app needs to remember something. My agent has written the code and shipped it, and now it needs a database. For a long time, that was where I took over. I'd click around a dashboard, copy a password, paste it into a.env file, and then paste it into the chat so the agent could use it.

By the end of that, the password lived in four places, and I'd chosen none of them on purpose. So I started wondering what it would look like if the agent could just ask for a database, wire it up, and never show the password to anyone, including me.

This post walks through how agentic-hosting does that. It's an open-source tool I built that turns a Linux server into a place where AI agents can deploy apps and databases on their own. By the end you'll know how to create a Postgres or Redis database with one API call, connect it to an app, keep its password out of chat logs, and remove it cleanly.

⭐ 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 and it can sign up and set things up from that one link.

agentic-hosting is one program, ah, that runs on your server. Agents talk to it over an HTTP API. Each agent (or team) is a tenant with its own API key, and an app it runs is called a service.

The examples below use two shell variables: AH_URL is your server's address and AH_KEY is the tenant's API key. The commands use curl and jq, which is also how an agent drives it.

One request creates a database. The type is postgres or redis:

IDEM = $( uuidgen ) # reuse this on retries curl -s -X POST " $AH_URL /v1/databases" \ -H "Authorization: Bearer $AH_KEY " \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $IDEM " \ -d '{"name":"notes-db","type":"postgres"}' Enter fullscreen mode Exit fullscreen mode The call doesn't come back until the database actually works. ah pulls the image ( postgres:16-alpine or redis:7-alpine ), starts a container, and then checks readiness by speaking the database's own protocol, not just by seeing an open port. Once the image is on the box this takes about 30 seconds. On a fresh server the pull can take longer, so the route allows up to six minutes.

That's why the Idempotency-Key header matters. Agents retry. If the connection drops halfway and the agent sends the same request with the same key, ah waits for the first one to finish and hands back its result, instead of making a second database. It remembers a successful result for 10 minutes.

You get back a 201 with something like this (trimmed):

{ "id": "3f9c0a…", "name": "notes-db", "type": "postgres", "status": "ready", "host": "ah-db-3f9c0a…", "port": 5432, "db_name": "ah", "username": "ah", "connection_string": "postgres://ah:…" } Enter fullscreen mode Exit fullscreen mode Names start with a letter, can use letters, numbers, hyphens, and underscores, and can be up to 63 characters. For Postgres, the database and user are both called ah. The password is 32 random bytes written as 64 hex characters. Nobody picks it.

This is the part I care about most. Before anything is saved, ah encrypts both the password and the full connection string with AES-256-GCM, using the server's master key. Only the encrypted versions go into ah 's own database.

The plain connection string shows up in exactly two places:

curl -s -H "Authorization: Bearer $AH_KEY " \ " $AH_URL /v1/databases/ $DB_ID /connection-string" # {"connection_string":"postgres://ah:…"} Enter fullscreen mode Exit fullscreen mode Every call to that endpoint writes an audit line to the server log with the tenant, the database, and the ID of the API key that asked. Listing or reading the database ( GET /v1/databases and GET /v1/databases/{id} ) never includes it.

Two honest notes. First, there's one master key for the whole server, and right now it encrypts everything directly: env vars, database passwords, connection strings. A design for deriving a separate key for each of those uses is in the repo but not built yet. Tenants are kept apart by the API, which scopes every lookup to the tenant's own records, not by separate keys. Second, "encrypted at rest" here means the credentials ah stores. The rows inside your Postgres sit in a normal Docker volume, and traffic between your app and the database is plain TCP ( sslmode=disable ) on a private network that only your services can join.

ah doesn't add DATABASE_URL to your app on its own. You set it, then restart. Those are two calls, and the connection string goes straight from one to the other without being printed:

CONN = $( curl -s -H "Authorization: Bearer $AH_KEY " \ " $AH_URL /v1/databases/ $DB_ID /connection-string" \ | jq -r.connection_string ) jq -n --arg u " $CONN " '{DATABASE_URL: $u}' \ | curl -s -X POST " $AH_URL /v1/services/ $SVC_ID /env" \ -H "Authorization: Bearer $AH_KEY " \ -H "Content-Type: application/json" -d @- curl -s -X POST \ " $AH_URL /v1/services/ $SVC_ID /restart" \ -H "Authorization: Bearer $AH_KEY " Enter fullscreen mode Exit fullscreen mode Setting env merges new keys into what's there. The response is only {"status":"updated",...}, with no values in it. Environment variables are encrypted with the same master key. If you read them back, every value comes back as ******** unless you add?reveal=true, and a reveal is also written to the audit log.

The restart matters more than it looks. It recreates the container, which is the only way new environment variables take effect, and it's also when the app gets connected to its database. More on that next.

For Redis it's the same flow. Use REDIS_URL as the name by convention, and the string looks like redis://:<password>@ah-db-<id>:6379/0.

Look at the host in the connection string: ah-db-<id>, not an IP address. That's on purpose, and it's something I got wrong at first.

Early versions handed out 127.0.0.1:<port>. That looks fine until you remember the app runs in its own container, where 127.0.0.1 is the container itself. The app was knocking on its own door. It was reported as issue #164 and fixed in October.

Now each database gets its own private Docker network, named ah-dbnet-<tenant>-<db>, with no route to the internet. On that network the database answers to the name ah-db-<id> on its normal port, 5432 or 6379. When a service is deployed, started, or restarted, ah looks at its environment variables. If one of them mentions one of that tenant's databases, ah connects the service to that database's network.

Two details follow from this. Only the tenant's own databases are checked, so another tenant can't get in by copying your connection string into its app. And the name only works from inside a service container. Running psql against it from the server itself won't resolve, and that's expected.

If you're upgrading from an older version, ah rewrites the old 127.0.0.1 strings on startup, both on the database and in any service env that copied it. Running apps still need one restart to pick up the new value. The fix is covered by tests in the repo, and the design note still lists a check on a live gVisor host as a to-do. If something doesn't connect for you, I'd like to hear about it.

All of the above is one command with ahc, the project's command-line client:

ahc db provision notes-api postgres Enter fullscreen mode Exit fullscreen mode It creates a database named notes-api-postgres (pick another with --name ), waits for it, sets DATABASE_URL (or REDIS_URL for Redis) on the service, and restarts it. It prints only the host, not the password. If you use Claude Code, the repo also ships an /ah-db slash command that does the same steps and is told never to print the full connection string.

In practice I mostly say "this app needs Postgres" and let the agent pick one of those.

This is deliberately one simple path, not a database service with every option:

Going past the quota returns a 409. If the disk is 90% full or more, creates fail with a 503 and a Retry-After header, so the agent knows to wait instead of retrying in a loop. There's no MySQL, no custom images, and no replicas or failover.

ah keeps your data in a Docker volume named ah-db-<id>, but it doesn't back that volume up. Its own backup command covers its control-plane state, not your app's rows. This is the part I hand to my agent, because it's a well-defined chore:

Every night, dump each Postgres database with pg_dump inside its container, gzip it, copy it to my backup host, and keep 7 days. Restore last night's dump into a scratch database once a week to prove it works. Alert me if a dump is missing or empty. Enter fullscreen mode Exit fullscreen mode The agent writes the timer, the script, and the restore check, and I review them once. The big platforms used to sell redundancy, backups, and monitoring as features. An agent can build the same pieces on your own server, and you can read exactly what it built.

Delete in the opposite order you created. Point the app away from the database first, so it isn't left with a broken URL:

curl -s -X DELETE -H "Authorization: Bearer $AH_KEY " \ " $AH_URL /v1/services/ $SVC_ID /env/DATABASE_URL" curl -s -X POST -H "Authorization: Bearer $AH_KEY " \ " $AH_URL /v1/services/ $SVC_ID /restart" curl -s -X DELETE -H "Authorization: Bearer $AH_KEY " \ " $AH_URL /v1/databases/ $DB_ID " Enter fullscreen mode Exit fullscreen mode The delete returns 204. ah stops and removes the container, then overwrites every file in the volume with zeros, so a later tenant on the same disk can't recover the data. Then it removes the volume, the record, and the private network. With the CLI it's ahc db delete notes-db --confirm, and the --confirm is required.

If something is left behind anyway, a background cleanup loop removes ah-db-* volumes that no database record owns, and database networks nothing owns once they're 15 minutes old.

An agent on agentic-hosting gets a working Postgres or Redis database with one call. The password is random, encrypted before it's stored, and only handed out through one audited endpoint, and the app reaches the database over a private network that's set up when it restarts. Pair it with a nightly dump your agent writes, and delete it cleanly when you're done.

Cover photo: storage tanks of the National Molasses Company on the Houston Ship Channel, with refineries behind. Blair Pittman, June 1973, for the EPA's Documerica project. U.S. National Archives, public domain.

Quelle: dev.to (dev.to). Original öffnen