Tell Your AI How It'll Be Deployed — One Prompt Block, Nine Fewer Bugs

·6 min read·Updated
aivibe-codingpromptingdeploymentdocker

Almost every problem people hit deploying an AI-built app traces to the same root cause: the model was never told where the code would run. It writes for a laptop — one user, a writable working directory, a dev server on localhost, secrets sitting in a config file — because that's the only environment it can see. The fix costs nothing and works better than fixing things afterwards: give it the deployment constraints up front, as part of the prompt. Here's a block you can paste into any coding session, and the reason each line is in it.

The block

Deployment target: a Linux container. Please follow these constraints.

  1. Put a Dockerfile at the repository root, named exactly that. No build arguments are available — every ARG must have a default, and nothing may depend on build-time environment variables.
  2. The server must bind 0.0.0.0 on one port, using a production server, not a dev server with hot reload.
  3. Read all configuration and secrets from runtime environment variables. Never put a secret in source, in the image, or behind a client-side prefix like NEXT_PUBLIC_ or VITE_.
  4. Only data written under a designated volume path survives a restart. Everything else in the filesystem is discarded when the container is recreated.
  5. Log to stdout/stderr, unbuffered. No log files.
  6. Exit with a non-zero code on unrecoverable errors. Don't wrap the app in your own retry loop — the platform restarts it.
  7. One process per container. No supervisord, no PM2, no cluster.
  8. Assume a fraction of one CPU core. Set thread and worker pool sizes explicitly and small; don't derive them from the host's CPU count.
  9. Assume the app is publicly reachable with no firewall or rate limiting in front of it.

Before writing code, list any of these that conflict with the approach you'd normally take.

That last line earns its place. Models will usually flag a genuine conflict — "you asked for one process but this framework needs a worker pool" — and that conversation is much cheaper before the code exists.

Why each line is there

1. Root Dockerfile, no build arguments. The build looks for exactly Dockerfile at the root of what you push, so a tidy docker/Dockerfile is never found. And no build arguments are passed, which means an ARG without a default resolves empty — and anything a framework inlines at build time silently becomes undefined. That's the NEXT_PUBLIC_ trap and its Vite equivalent, and it produces no error at all.

2. Bind 0.0.0.0, production server. A server on 127.0.0.1 is reachable only from inside its own container. Nothing probes your app to catch this, so it deploys "successfully" and never answers. Dev servers add hot reload and file watchers you don't want, and often default to loopback.

3. Secrets from runtime environment. ENV values live in image layers and are readable by anyone who can pull the image. A client-side prefix is worse — those values are compiled into JavaScript sent to the browser. Rotate anything that's already shipped.

4. Volumes or nothing. Every configuration change and every deploy recreates the container. An SQLite file next to your code is gone on the next push, and models put it there by default because that's what works locally.

5. Logs to stdout. A log file inside a container is a diagnostic nobody can read, discarded exactly when you'd want it. Add "unbuffered" explicitly — Python buffers stdout when it isn't a terminal, which makes a crashing app look silent.

6. Exit on unrecoverable errors. Restarts are triggered by your process exiting. A try/except that swallows a fatal error leaves you running and useless, and nothing here probes your app to notice. A self-written retry loop hides the exit code and, for a Discord bot, multiplies a rate-limited failure.

7. One process. Two processes need a supervisor, and a supervisor inside the container means the platform sees a healthy wrapper while the thing that matters is dead. It also replaces your app's exit code with its own.

8. Small, explicit concurrency. A container sees the host's cores, not your quota, and several runtimes size their thread pools from that — Python's default executor, Node's cluster, tokio's worker count. Your CPU allocation is a time budget shared by the whole container, so more workers exhaust it faster.

9. Publicly reachable. This is the one that changes what code the model writes rather than how it's packaged. Told that the app is exposed, models add authentication to write routes and think about limits — which they mostly skip when the implied audience is you. It won't produce a hardened app, but it moves the security review from "six findings" to "two".

Use it twice

Paste it at the start of the project, and again when you ask for the Dockerfile. Long sessions drift, and containerisation usually happens well after the constraints were mentioned — by which point they're far up the context and easy to lose.

Two habits that help:

  • Keep it in the repository, as DEPLOYMENT.md or in your agent's rules file, so it's loaded rather than remembered.
  • Ask for a conflicts list, not just code. Asking what the model would normally do differently surfaces the framework-specific traps you don't know to ask about.

It doesn't replace checking

Constraints in the prompt reduce failures; they don't eliminate them. A model can acknowledge every line and still write EXPOSE 3000 and think the port is published.

So run the pre-deploy checklist before your first push, and after deploying, verify with artefacts rather than a summary — logs, a request from inside the container, a request from outside. If you push with git, the build output tells you most of what you need.

FAQ

Why does AI-generated code fail when deployed but work locally?

It was written for a laptop — localhost binding, a writable directory, secrets in files. Nothing about the container target was in the prompt.

What's the single most useful constraint to give a model?

Bind 0.0.0.0 and use a production server. Localhost binding is the most common silent failure, because the deploy still succeeds.

Should I mention the Dockerfile location?

Yes. The build reads only Dockerfile at the root of what you push, and models default to tidy subfolders.

Why tell it the app is publicly reachable?

It changes what gets written. Models add auth and limits when there's an implied adversary and mostly omit them when there isn't.

Does this work with any coding assistant?

Yes — it's a plain list of environment facts, not a tool-specific trick. Keeping it in the repo works better than re-typing it.

Do I still need to review the output?

Yes. This reduces the failure list; it doesn't remove it. Check the Dockerfile and verify the deploy with real output.

Wrapping up

The gap between "runs on my machine" and "runs when deployed" is mostly a gap in what the model was told. Nine sentences close most of it, and they cost one paste at the start of a session instead of an evening of debugging afterwards. Keep the block in your repository, hand it over again when you ask for the Dockerfile, and ask the model what it would have done differently — that last question is where the useful conversation happens.