{
"$type": "site.standard.document",
"description": "Why I run small products on one Hetzner server with Kamal, and how Agent Control serializes deployments when coding agents are doing the work.",
"path": "/how-kamal-fits-my-coding-agent-workflow/",
"publishedAt": "2026-07-05T06:12:00.000Z",
"site": "at://did:plc:bryys25pc2fnagnyxqgsglhd/site.standard.publication/3mn26bjkkmh23",
"tags": [
"Web",
"Tools",
"AI"
],
"textContent": "I run a bunch of small products, and most of the work now happens through coding agents. That changes what I want from deployment.\n\nClicking around in a deployment platform is the wrong interface for that. I want deployment to be visible in the repo, scriptable from a terminal, and predictable when several agents are working at once.\n\nFor my current setup, Kamal is enough.\n\nTHE SETUP IS ONE PHYSICAL SERVER\n\nMy current pattern is simple: one Hetzner server, Docker containers, Kamal configs in each repo, and PostgreSQL on the same box.\n\nThe setup is intentionally small: one solo operator running small products that need to stay cheap and understandable.\n\nThe repo evidence is plain:\n\n * MyOG.social deploys web, backend, and api roles to the same host.\n * TheBlue.social deploys web, backend, feed, and worker roles to the same host.\n * TheBlue's AGENTS.md says it migrated from Render to Hetzner with Kamal 2 on April 8, 2026.\n * TheBlue shares that server and the PostgreSQL accessory with MyOG.social and AltCaption.com.\n * MyOG's Kamal config defines the shared PostgreSQL accessory and the Vector logging accessory.\n\nI don't set up a new database server for every tiny product. MyOG was the first project in this setup, so it owns the shared server pieces. The newer projects follow the pattern instead of inventing their own.\n\nMYOG WAS THE FIRST ONE\n\nMyOG.social has the older Kamal setup in my repos. Its history shows the move in December 2025:\n\n2025-12-20 Add Kamal 2 deployment configuration for Hetzner with PostgreSQL, Vector logging, SSL, and pre-rendering support\n2025-12-19 Remove legacy render.yaml now using Kamal on Hetzner\n\nThe config is what I like about Kamal. The deployment state is a file:\n\nservice: myog\n\nservers:\n web:\n hosts:\n - <server-ip>: frontend\n\n backend:\n hosts:\n - <server-ip>: backend\n proxy:\n ssl: true\n host: backend.myog.social\n app_port: 3001\n\n api:\n hosts:\n - <server-ip>: api\n proxy:\n ssl: true\n host: api.myog.social\n app_port: 3002\n\naccessories:\n postgres:\n image: postgres:16-alpine\n host: <server-ip>\n\n vector:\n image: timberio/vector:latest-alpine\n host: <server-ip>\n\nI trimmed that snippet, but the important parts are there: roles, host, proxy, app ports, accessories.\n\nKamal's own docs describe the same model: roles live under servers, accessories are managed separately from the app, and the proxy routes traffic to the new container after the health check passes.\n\nThat maps well to how I want agents to work. If a deploy fails, the agent can read config/deploy.yml, compare it with the Dockerfile and runtime scripts, inspect logs, and make a repo change.\n\nNo special dashboard integration needed.\n\nTHEBLUE MIGRATED LATER\n\nTheBlue.social moved later, after the MyOG setup had already proved itself.\n\nIts AGENTS.md records the date directly:\n\nMigrated from Render to Hetzner (Kamal 2) on 2026-04-08.\nShares the Hetzner server and Postgres accessory\nwith MyOG.social and AltCaption.com.\n\nTheBlue is also a good example of the pattern growing without turning into a platform.\n\nIt started as a web/backend app, then picked up more moving parts: feed service, background workers, and a lot of pre-rendered SEO pages. The Kamal setup split with it:\n\nconfig/deploy.yml # web\nconfig/deploy.backend.yml # backend + workers\nconfig/deploy.feed.yml # custom feed service\n\nThe web deploy has Caddy serving the built frontend and host-mounted pre-rendered HTML releases. Backend deployment covers the API and workers. Feed has its own image and service.\n\nStill the same idea: Docker images, Kamal configs, one server, explicit roles.\n\nThe deploy script detects changed areas and deploys the right targets:\n\nscripts/push-and-deploy.sh\nscripts/push-and-deploy.sh --force\nkamal deploy --roles=web\nkamal deploy -c config/deploy.backend.yml\nkamal deploy -c config/deploy.feed.yml\n\nIt also handles a practical rule I care about: push to GitHub after deploy succeeds. If production deploy fails, origin/main should not advance as if everything shipped.\n\nThat rule matters more once agents are allowed to deploy. If production deploy fails, I want the repo state to say that clearly too.\n\nWHY THIS WORKS WITH CODING AGENTS\n\nCoding agents are good at text, diffs, commands, and logs. Kamal gives them exactly that.\n\nA deployment problem usually becomes one of these:\n\n * a missing secret name in .kamal/secrets or config/deploy.yml\n * a bad health check path\n * a Docker build issue\n * a runtime script that starts the wrong process\n * a frontend build-time env var that was treated like a runtime env var\n * a role split that needs a different deploy config\n\nAn agent can inspect those repo problems.\n\nMy project AGENTS.md files tell agents how to deploy, which package manager to use, how long web builds usually take, what not to print, and when to run the deploy in tmux with a callback.\n\nThat last part is important because deploys are long-running enough to be annoying inside a chat turn. The TheBlue instructions say web deploys have taken about 6.7 minutes, and that Vite can sit quietly at rendering chunks... for several minutes. The point of writing that down is to stop an agent from killing a deploy just because nothing printed for a while.\n\nI want the agent to know that before it touches the system.\n\nTHE DEPLOYMENT LOCK HANDLES THE SHARED SERVER\n\nOne server is simple, but RAM is finite.\n\nIf I have three coding agents working on three products, I do not want all three running Kamal deploys against the same server at the same time.\n\nAgent Control handles that with a shared deploy lock:\n\ndeploy:hetzner-main\n\nThe rule is: acquire the lock before starting a Hetzner/Kamal deploy, keep it through verification, then release it. If another session already has it, the new deploy waits.\n\nThe actual workflow is still the normal project deploy. The lock wraps it.\n\nEach repo still owns its deploy command. Agent Control serializes access to the shared server.\n\nSo the flow becomes:\n\nagent finishes change\nagent runs checks\nagent commits\nagent asks for deploy lock\nAgent Control queues it if another deploy is running\nagent deploys in tmux when lock is acquired\nagent verifies production\nagent releases lock\nagent reports back\n\nThis is the part that makes one server work with agent-driven development. The server stays simple, but the deployment jobs are serialized.\n\nWHERE I DRAW THE LINE\n\nOne server has limits.\n\nIf the server is down, multiple products are affected. More capacity means moving pieces around. A product that grows a lot may need to leave the shared setup.\n\nThat is fine for where these products are now.\n\nMost small products do not need a platform on day one. They need backups, logs, health checks, repeatable deploys, and a setup the owner understands. My setup has those pieces without making me operate a deployment platform.\n\nKamal is not magic. It still uses Docker, SSH, a registry, env files, health checks, and a proxy. The useful part is that those pieces are explicit and close to the code.\n\nThat fits my work better than a sprawling platform.\n\nWHAT I KEEP REUSING\n\nThe pattern I keep copying into newer projects:\n\n * one Kamal config per deployable boundary when the app grows\n * web, backend, feed, or worker roles instead of one giant container\n * .kamal/secrets reading from .env.kamal, without committing values\n * app-specific AGENTS.md deploy notes\n * push/deploy scripts that detect changed services\n * tmux for deploys that take minutes\n * Agent Control callbacks after long-running commands\n * deploy:hetzner-main for shared-server deploy serialization\n\nFor my current small-products life, this is enough infrastructure.\n\nI can tell a coding agent to fix something, run the checks, deploy it, verify it, and report back. The deploy path is plain text the agent can read and reason about.",
"title": "How Kamal Fits My Coding-Agent Workflow"
}