Home/Blog/How to Write a Multi-Stage Dockerfile with a Pinned Base Image
Blog

How to Write a Multi-Stage Dockerfile with a Pinned Base Image

P
promptstudio
How to Write a Multi-Stage Dockerfile with a Pinned Base Image

Dockerfiles fail in CI when the prompt writes FROM golang:latest and EXPOSE 8080. Your service listens on 8443. Your pin was 1.23.6-bookworm. Honor both.

The matching generator is the Multi-Stage Dockerfile from a Service Spec (Version-Locked Base Image) prompt. Browse related cards in the PromptDig library (Browse more prompts). When a filled run survives, share the version you actually use (Share a prompt).

Quote the builder and runtime pins first

Write a multi-stage Dockerfile from a service spec with a pinned base image digest or tag. No floating latest, no invented ports. Start by filling Inputs, not by asking the model to remember last week's run. If a field is blank, write NONE or NOT IN INPUTS and leave it blank through Generate. The card is built so the model cannot honestly invent a number, owner, URL, or command that you did not paste.

Paste these fields before you hit run:

Service name, language, and version: [Service]
Base image with exact tag or digest: [Base]
Runtime image with exact tag or digest: [Runtime]
Build commands I actually run: [Build]
Runtime command and working directory: [Cmd]
Ports the process listens on: [Ports or NONE]
Files to copy (include/exclude): [Files]
User the process must run as: [User or NONE]
BuildKit features I allow (or NONE): [Buildkit]
Words I must not use: [Banned]

That inventory is the honesty ledger. Anything that does not appear there is forbidden in the draft. If you catch yourself adding a nice-to-have after the run, you are no longer using the card. You are ghostwriting. Put the extra fact in Inputs and run again.

COPY only the paths in the spec

Generate is numbered on purpose. Do not skip a step because the first paragraph looked done. The early steps exist to stop later prose from smuggling claims.

Walk the Generate list in order:

  1. Version lock: quote Base and Runtime. Refuse :latest and any tag not in Inputs. If digest is missing, say tag-only pin and do not invent a sha256.
  2. Stage map: builder and runtime. Name stages. List COPY --from sources from Files only.
  3. Dockerfile: syntax line only if Buildkit names a syntax image. FROM pins exact. RUN only Build commands. No apt packages unless Build names them.
  4. USER, WORKDIR, EXPOSE, CMD/ENTRYPOINT from User, Cmd, Ports. If Ports is NONE, omit EXPOSE rather than guessing 8080.
  5. .dockerignore sketch from Files exclude list only.
  6. Build and run commands the operator can paste, using the pins. No invented registry.
  7. Gaps: secrets, healthcheck, non-root user, digest, if missing.
  8. Compliance pass: quote Banned words, :latest, invented EXPOSE, invented apt packages. Cut them.

If a step asks for a version lock, quote the version from Inputs in the output. If a step asks for a refuse list, keep the refuse list in the published artifact, not in a sidebar you delete. Reviewers should see what the model was not allowed to do.

Omit EXPOSE when ports are NONE

Most failures are the same shape: a missing field gets a confident fill. A conversion rate appears. A Gradle task appears. A flash point appears. A caption appears on a job that asked for slide text only. Your review is to search the draft for numbers, names, and commands, then grep Inputs. No match means cut.

Honor the constraints as hard stops, not vibes:

  • Multi-stage Dockerfile. Do not emit a Compose file unless Inputs include one.
  • Never float :latest. Never invent a digest.
  • Do not add curl, bash, or debug shells unless Build names them.
  • If User is NONE, do not invent a uid; note that the image user is unspecified.
  • No emojis.

When the card says not legal advice, not certification, not an exam dump, or not a caption engine, that sentence belongs at the top of the output. Deleting it to look more finished is how you inherit risk.

No debug shells unless the spec named them

Finish with the compliance pass the prompt already asks for. Quote the banned-word hits. Cut them. Print character counts when the job has a cap. Print word counts when the job has a budget. List gaps as gaps. Five missing facts are more useful than one smooth paragraph.

Tags on the card (multi stage dockerfile, pinned base image tag, dockerfile from service spec) are a reminder of the job shape, not an invitation to wander into a neighboring cluster. If you need a different surface, open a different PromptDig card rather than stretching this one.

Fill the card, then run

Replace every bracket. Run on ChatGPT, Claude, or Gemini. Read the ledger first, then the artifact. If the model invents a commit, KPI, DOI, PEL, bid, or logo, discard the run. Tighten Inputs. Run again. Share the filled card that survived, not the first draft that sounded done.