AI Prompts for Writing Dockerfiles and Container Configs

undefined

AI can write you a working Dockerfile in seconds, but “working” and “production-ready” are not the same thing. ChatGPT and Claude are very good at producing a Dockerfile that builds and runs your app, and just as likely to hand you one that runs as root, skips multi-stage builds, and pins nothing. The fix isn’t avoiding AI for container configs — it’s giving it the right context up front and knowing exactly which follow-up prompts to fire when the first draft comes back sloppy.

What context should you give the AI before it writes a Dockerfile?

A generic prompt like “write me a Dockerfile for my app” produces a generic Dockerfile. The model has no idea whether you’re shipping a Python API, a Node build with a compile step, or a Go binary that doesn’t even need a runtime beyond scratch. Before you ask for any code, hand the AI the same information you’d give a teammate doing a code review: language and runtime version, the framework, how the app starts, which ports it listens on, and what it depends on at build time versus run time.

I need a production Dockerfile for a Node.js 20 Express API.
- Package manager: npm, with a package-lock.json
- Build step: none (no TypeScript compile, plain JS)
- App listens on port 3000
- Runtime dependencies only from package.json "dependencies" (not devDependencies)
- Needs to run as a non-root user
- Should use a multi-stage build to keep the final image small
- Base image: an official, actively maintained Node LTS image
- Include a HEALTHCHECK against a /health endpoint
- Also generate a matching .dockerignore

Explain each instruction in a comment above it.

Notice what this prompt does: it names the exact base language version instead of leaving it to guess, it separates build-time and run-time dependencies, and it asks for a .dockerignore in the same breath as the Dockerfile. Skipping .dockerignore is one of the quieter mistakes AI output tends to make — without it, your build context (and sometimes your image) picks up node_modules, .git, .env files, and local build artifacts that have no business being copied into a container.

How do you get AI to write a proper multi-stage build instead of one fat image?

Docker’s own build documentation is explicit about this: split your Dockerfile into distinct stages so the final image only contains what’s needed to run the app, not everything that was needed to build it. A single-stage Dockerfile that runs npm install, a compiler, or a full SDK inside the image you actually ship will drag along build tools, caches, and source files nobody needs at runtime — that’s dead weight and extra attack surface.

If the AI’s first draft is a single FROM block, push back with a direct prompt:

Rewrite this as a multi-stage build:
- Stage 1 ("builder"): install dependencies and build the app
- Stage 2 (final): use a slim/alpine runtime base, copy only the
  built artifacts and production dependencies from the builder stage
  using COPY --from=builder
- Name each stage with FROM ... AS so the COPY --from references
  survive if instructions get reordered later

For a compiled language this is even more dramatic: a Go or Rust build stage can use the full SDK image, and the final stage can copy just the compiled binary into a minimal or even scratch base, leaving the entire toolchain behind. That’s the difference between an 900MB image and a 20MB one, and it’s a one-line follow-up prompt away once you know to ask for it.

Why does AI keep generating Dockerfiles that run as root, and how do you fix it?

Most base images default to the root user, and most AI-generated Dockerfiles never override that, because a container that runs as root “just works” in a demo. It’s also a real security problem: if an attacker breaks out of your app process, running as root inside the container makes privilege escalation and host-level damage much easier. This is consistently the single most common issue in AI-drafted Dockerfiles — ask any model for a quick Dockerfile and check whether it added a USER instruction. Usually it didn’t.

The fix is a short, specific prompt:

This Dockerfile runs as root. Add a dedicated non-root user and
switch to it before the app starts:
- Create a system user/group (e.g. addgroup/adduser on Alpine,
  or groupadd/useradd on Debian-based images)
- Use COPY --chown so app files are owned by that user, not root
- Add a USER instruction before the CMD/ENTRYPOINT
- Make sure any ports below 1024 aren't required, or explain the
  workaround if they are

Some official images already ship a non-root user you can reuse instead of creating your own — the Node images, for example, include a node user out of the box. It’s worth explicitly asking the AI “does this base image already include a non-root user I can use with USER, instead of creating a new one?” so it doesn’t reinvent something that already exists.

How do you stop AI from using unpinned, “latest” base images?

Ask for “a Node Dockerfile” and you’ll often get FROM node:latest or, at best, FROM node:20. Both are moving targets: latest can change entirely between builds, and even a major-version tag like 20 gets new patch releases pushed under the same name. That’s fine for a quick experiment and a real problem for reproducible builds — the image that passed your tests on Tuesday isn’t necessarily the image that ships on Friday.

Push the AI to pin properly:

Pin the base image to a specific, immutable version instead of a
floating tag. Prefer a minimal variant (alpine or slim) and show me
how to pin by digest as well as by version tag, for example:
FROM node:20.17.0-alpine3.20
FROM node:20.17.0-alpine3.20@sha256:
Also pin any package versions installed with apt-get/apk to specific
releases rather than "latest".

Pinning by digest, as Docker’s build documentation notes, guarantees your build uses the exact same image content every time, even if the publisher later re-pushes a different image under the same tag. It’s a small addition that closes a real supply-chain gap AI drafts almost never account for on their own.

What should you ask AI to check in a docker-compose file?

The same pattern — vague prompt in, sloppy config out — applies to docker-compose.yml. AI-generated compose files commonly skip health checks between dependent services (so your app container starts before the database is actually ready), hardcode secrets directly into environment variables, and forget to scope volumes and networks tightly. Once you have a first draft, run it back through a review prompt:

Review this docker-compose.yml and fix:
1. Add a HEALTHCHECK-based `depends_on: condition: service_healthy`
   so the app waits for the database to be ready, not just started
2. Move any secrets (passwords, API keys) out of environment values
   and into an .env file referenced with env_file, excluded from git
3. Pin every image tag to a specific version, none of them "latest"
4. Make sure volumes are named and scoped, not bind-mounting the
   whole project directory into a production-style service
5. Confirm each service that doesn't need to be internet-facing has
   no published ports

Treat that as a checklist you run against every AI-generated Dockerfile and compose file, not a one-time fix. The same five or six issues — root user, unpinned base images, missing multi-stage builds, missing .dockerignore, secrets in plain env vars, and no health checks — show up again and again because they’re the parts a model optimizes away by default when it’s just trying to produce something that runs.

Frequently Asked Questions

Is it safe to use AI-generated Dockerfiles in production?

It’s safe once you’ve reviewed it against the same checklist you’d apply to a human-written one: non-root USER, pinned base image versions, a multi-stage build that excludes build-only tools, a .dockerignore file, and no secrets baked into the image or committed to environment variables. AI is a fast way to get a first draft; it isn’t a substitute for that review pass, because the default output tends to skip exactly those items.

Should I ask AI for Alpine or Debian-based (slim) images?

Both are reasonable starting points, and which one is “better” depends on your app. Alpine images are smaller and use musl libc, which occasionally causes compatibility issues with native Node or Python packages compiled against glibc; Debian-based “slim” images are a bit larger but behave more predictably for those cases. A good prompt is to ask the AI directly: “this app uses native dependencies — recommend alpine or slim and explain the tradeoff for this specific stack,” rather than defaulting to whichever one it mentions first.

How do I get AI to explain a Dockerfile it already wrote for me?

Paste the Dockerfile back and ask it to annotate every instruction: “add an inline comment above each line explaining what it does and why it’s in this position (for example, why COPY package.json happens before COPY . to preserve Docker’s build cache).” This turns a Dockerfile you copy-pasted into one you actually understand, which matters the next time you need to debug a broken build or extend it for a new environment.

For more on directing AI output with the right upfront context, see How to Get Structured JSON Output From AI Models: A Prompting Guide and AI Code Review Prompts: How to Catch Bugs Before They Ship for a similar checklist-driven approach applied to reviewing code changes before they ship. On the Docker side, the official Docker build best practices guide and the multi-stage builds documentation are worth bookmarking as the reference you check AI output against.

Featured photo by AgainErick, licensed under CC BY-SA 4.0, via Wikimedia Commons.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top