- name
- generate-dockerfile
- description
- Generate or enhance a Dockerfile for a containerized application using curated base-image recommendations and security best practices. Use AFTER analyze-repo (or after the user has told you the language/framework). Triggers include "generate a Dockerfile", "containerize this", "write Dockerfile for X", or when running aks-loop and no Dockerfile yet exists. If a Dockerfile already exists, this skill enhances it in place.
- argument-hint
- <modulePath> <language> [framework] [environment=production|development]
# Generate Dockerfile
Produce a production-quality, secure Dockerfile (or enhancement plan for an
existing one) for a single module. The output is a complete Dockerfile that
the user can write to disk — not a description of one.
## Inputs
| Field | Required | Description |
|---|---|---|
| `modulePath` | yes | Absolute path of the module to containerize (one of the modules from `analyze-repo`). |
| `language` | yes | `java` \| `dotnet` \| `javascript` \| `typescript` \| `python` \| `go` \| `rust` \| `php` \| `ruby`. |
| `languageVersion` | recommended | E.g. `21`, `8.0`, `20`, `3.11`. Substituted into the base image tag. |
| `framework` | optional | E.g. `spring-boot`, `express`, `next`, `django`, `flask`, `asp.net-core`, `gin`. |
| `environment` | optional | `production` (default) or `development`. |
| `targetPlatform` | optional | `linux/amd64` (default) or `linux/arm64`. |
| `detectedDependencies` | optional | Used to choose extra apt/apk packages (e.g. native modules). |
| `detectedEnvVars` | optional | List from `analyze-repo`. Used to add `ENV` instructions for `config`/`database` vars and to WARN about `secret`-classified ones (never bake secrets in). |
| `existingDockerfile` | optional | If a Dockerfile is already at `<modulePath>/Dockerfile`, read its content and follow the **Enhancement path** instead of the **Generation path**. |
## Prerequisite: analyze-repo
This skill needs `language`, `framework`, `detectedDependencies`, and
`detectedEnvVars` from the `analyze-repo` skill. If you don't already
have that output for `<modulePath>` from earlier in this conversation,
follow the `analyze-repo` skill against `<modulePath>` first, then come
back.
## Decision: generate or enhance?
1. Check if `<modulePath>/Dockerfile` exists.
2. If **no** → Generation path (Step G1–G4 below).
3. If **yes** → read the file, run **Dockerfile analysis** (Step E1), then
Enhancement path (Step E2–E4).
---
## Generation path
### G1 — Choose build strategy
Use **multi-stage** if any of these:
- `language` ∈ {`java`, `go`, `rust`, `dotnet`}
- `language` is `typescript` AND build output is required (`tsc`, `next build`, `vite build`, etc.)
- `buildSystem.type` ∈ {`maven`, `gradle`}
Otherwise use **single-stage**.
The reason this matters: multi-stage drops compilers, SDKs, and source code
from the final image — typically 70–90% smaller and a smaller attack surface.
### G2 — Choose the base image(s)
**Hard rule: every `FROM` line MUST start with `mcr.microsoft.com/` unless
the stack appears in the explicit non-MCR list (G2.3) below.**
The Dockerfile is the contract for an Azure-hosted image. Public-registry
images (`eclipse-temurin`, `tomcat`, `node`, `python`, `golang`, `rust`)
are **not** "alternatives" — they are fallbacks for the few stacks where
MCR genuinely does not publish a base. Pick from the catalog in G2.1
first; only if no row matches, drop to G2.3.
#### G2.1 — MCR catalog (use these tags, do not invent others)
Use only the tags listed here. If your `<LV>` is not in the list, see
G2.2 (modernize) or G2.3 (documented fallback) — do NOT guess a tag.
**Java — `mcr.microsoft.com/openjdk/jdk`**
| `<LV>` | Build tag | Runtime tag |
|---|---|---|
| 8 | `8-azurelinux` | `8-distroless` |
| 11 | `11-azurelinux` | `11-distroless` |
| 17 | `17-azurelinux` | `17-distroless` |
| 21 | `21-azurelinux` | `21-distroless` |
| 25 | `25-azurelinux` | `25-distroless` |
These are the only Java major versions on MCR (verified against
`https://mcr.microsoft.com/v2/openjdk/jdk/tags/list`). The published tag
format is a **single digit major** — `8-azurelinux`, never `1.8-azurelinux`
or `8u292-jdk-slim` (those do not exist and fail with `image not found`).
MCR does **not** publish a separate `openjdk/jre` repository — for the
runtime stage of a JRE-only image, use the `-distroless` variant of the
same major (`8-distroless`, `17-distroless`, …) which ships only the JRE.
**.NET — `mcr.microsoft.com/dotnet/{sdk,aspnet,runtime}`**
| `<LV>` | SDK | ASP.NET runtime | Generic runtime |
|---|---|---|---|
| 8.0 | `sdk:8.0-azurelinux3.0` | `aspnet:8.0-azurelinux3.0` | `runtime:8.0-azurelinux3.0` |
| 9.0 | `sdk:9.0-azurelinux3.0` | `aspnet:9.0-azurelinux3.0` | `runtime:9.0-azurelinux3.0` |
**Node.js — `mcr.microsoft.com/azurelinux/{base,distroless}/nodejs`**
| `<LV>` | Build tag | Runtime tag |
|---|---|---|
| 18 | `base/nodejs:18` | `distroless/nodejs:18` |
| 20 | `base/nodejs:20` | `distroless/nodejs:20` |
| 22 | `base/nodejs:22` | `distroless/nodejs:22` |
**Python — `mcr.microsoft.com/azurelinux/{base,distroless}/python`**
| `<LV>` | Build tag | Runtime tag |
|---|---|---|
| 3.11 | `base/python:3.11` | `distroless/python:3.11` |
| 3.12 | `base/python:3.12` | `distroless/python:3.12` |
Defaults if `languageVersion` is missing: `java=21`, `dotnet=8.0`,
`node=20`, `python=3.12`.
#### G2.2 — Modernize before falling back
If the project targets a version not in G2.1 (typically Python 3.9/3.10,
Node 16, Java 7):
1. **Preferred:** bump the build target to the nearest LTS in G2.1
(Python → 3.11, Node → 18). Update package metadata, keep MCR images.
2. Only if modernization is genuinely impossible, drop to G2.3 and
document the reason in a `# WHY-NOT-MCR:` comment above the
offending `FROM` line.
#### G2.3 — Allowed non-MCR fallbacks (closed list)
The following bases may appear when, and only when, no MCR row in G2.1
applies. Anything not in this list MUST NOT appear in a `FROM` line.
**Hard rule — build images are build-only.** The images in the **Build
stage** column below (`maven:*`, `gradle:*`, `golang:*`, `rust:*`,
`composer:*`) are builders. They MUST NOT be the final / runtime stage. Using
a non-MCR build image obligates a **multi-stage** Dockerfile whose final
`FROM` is the matching **Runtime stage** image (an MCR `*-distroless` row from
G2.1 wherever one exists). A single-stage Dockerfile that ships a
`maven` / `gradle` / `golang` / `rust` builder as the runtime image is a policy
violation: it defeats the MCR-runtime goal and ships the full build toolchain
plus source in the running image — even though a lone `maven`/`gradle` `FROM`
looks "allowed" here.
| Stack / Reason | Build stage | Runtime stage |
|---|---|---|
| Maven builder (MCR ships no Maven-with-JDK image) | `maven:3.9-eclipse-temurin-<LV>` (where `<LV>` ∈ {8, 11, 17, 21}) | use the matching MCR `openjdk/jdk:<LV>-distroless` for runtime |
| Gradle builder (MCR ships no Gradle image) | `gradle:<ver>-jdk<LV>` | use the matching MCR `openjdk/jdk:<LV>-distroless` for runtime |
| Java EE / Jakarta EE app server (EJB, JSF, full profile — MCR ships no app server) | `maven:3.9-eclipse-temurin-<LV>` or MCR `openjdk/jdk:<LV>-azurelinux` | `jboss/wildfly:<ver>` (WildFly), `payara/server-full:<ver>` (Payara), `tomee:<ver>` (TomEE), or `tomcat:<ver>-jdk<LV>` (servlet-only WAR) |
| Servlet/JSP WAR with no EJB | MCR `openjdk/jdk:<LV>-azurelinux` | `tomcat:<ver>-jdk<LV>` |
| Go (no MCR base) | `golang:<LV>-alpine` | `gcr.io/distroless/static-debian12:nonroot` (CGO off) or `gcr.io/distroless/base-debian12:nonroot` (CGO on) |
| Rust (no MCR base) | `rust:<LV>-slim` | `gcr.io/distroless/cc-debian12:nonroot` |
| PHP (no MCR base) | `composer:2` | `php:<LV>-fpm-alpine` or `php:<LV>-apache` |
| Ruby (no MCR base) | `ruby:<LV>-slim` | `ruby:<LV>-slim` |
**Bare JDK/JRE images (`eclipse-temurin`, `openjdk`, `adoptopenjdk`,
`amazoncorretto`, `ibmjava`) are NOT on this list.** For Java versions
8, 11, 17, 21, or 25, the MCR `openjdk/jdk:<LV>-{azurelinux,distroless}`
row in G2.1 is mandatory — falling back to `eclipse-temurin:8-jre-alpine`
or `adoptopenjdk:8-jre-hotspot` when MCR has the same major is a policy
violation, even with a `# WHY-NOT-MCR:` comment.
**Tag-name precision for Docker Hub `maven` images:** the only published
`maven:3.9-eclipse-temurin-*` tags are `8`, `11`, `17`, `21` — there is
**no** `…-jdk` suffix on these. Tags such as `maven:3.9-eclipse-temurin-8-jdk`
or `maven:3.9-eclipse-temurin-17-jdk` do **not** exist and fail with
`not found`. Use `maven:3.9-eclipse-temurin-17`, not
`maven:3.9-eclipse-temurin-17-jdk`.
When you use any row from G2.3, prepend a `# WHY-NOT-MCR:` comment
above the `FROM` line explaining which condition triggered the
fallback (e.g. `# WHY-NOT-MCR: Java EE app server, no MCR equivalent`).
**Never invent a tag.** If you are tempted to write
`mcr.microsoft.com/<anything>:<LV>-<suffix>` and the exact tag is not
in G2.1, stop — either modernize per G2.2 or fall back per G2.3.
### G3 — Construct the Dockerfile
**Hard rule: every Dockerfile MUST contain
`LABEL com.azure.containerizationassist.createdby="containerization-assist"`,
emitted on the line immediately after the `FROM` of the runtime / final
stage. No exceptions. A Dockerfile without this label is rejected.**
Apply every rule in this checklist:
**Layer/structure**
- One `WORKDIR` early (use `/app`); never use `cd` in `RUN`.
- Copy **dependency manifests first**, install deps, then copy source — to
preserve layer cache.
- **Source MUST be copied before any build/compile command.** Every build
stage that runs `mvn` / `./mvnw` / `gradle` / `./gradlew` / `npm run build`
/ `tsc` / `dotnet publish` / `go build` / `cargo build` MUST be preceded
by a `COPY src ./src` (or equivalent — `COPY . .` for languages without
a conventional `src/` layout). Skipping the source copy produces an empty
build that fails with errors like "Unable to find main class" or
"no main module" at runtime.
- Use `RUN` chaining with `&&` to keep layers small. Aim for ≤ 6 `RUN`
instructions.
- For multi-stage, give each stage a name (`AS build`, `AS runtime`).
**Build output & dependencies (derive, never assume)**
Where a build writes its artifact, and which dependencies it needs to run,
are determined by the project's own config — not by framework convention.
Two recurring build failures come from guessing instead of reading:
- **Derive the build-output directory; never hardcode it.** Before you
`COPY` build output into the runtime stage, find where the build tool
actually writes it:
1. Read the build tool's config for a configured output path — e.g.
`build.outDir` in `vite.config.*`, `compilerOptions.outDir` in
`tsconfig.json`, `outputPath` in `angular.json`, `distDir` in
`next.config.*`, `output.path` in `webpack.config.*`, `buildDir` in
`nuxt.config.*`.
2. If none is set, use that tool's documented default.
3. If still unsure, run the build in the build stage and `COPY` the
directory it actually produced.
`build/` is only Create React App's default. Vite, Vue CLI, Angular and
most bundlers emit to `dist/`; Next.js to `.next/`. Hardcoding
`COPY build/ ...` fails with `"/app/build": not found` on any of them.
- **Install full dependencies in the build stage; prune only for runtime.**
Compilers and bundlers (`tsc`, `vite`, `webpack`, `rollup`, `esbuild`)
almost always live in `devDependencies`. In the build stage run a full
install (`npm ci` / `npm install` with **no** `--omit=dev` /
`--production`). Running `npm ci --omit=dev` before `npm run build`
removes the compiler and fails with `tsc: not found` / exit 127. Only the
**runtime** stage may drop dev deps — or, better, copy just the built
artifact into a clean runtime image and install nothing there.
These two rules hold for any compiled or bundled stack — present or future
— so they replace per-framework "copy from X" recipes rather than adding to
them.
**Security baseline (mandatory)**
- Final stage MUST end with a `USER` instruction set to a non-root user.
- If the base image already provides one (e.g. `nonroot`, `appuser`), use
it; otherwise create one with **two separate instructions** (`USER` is a
Dockerfile instruction, not a shell command — it cannot be chained after
`RUN ... &&`):
- Alpine:
```
RUN adduser -D -u 10001 appuser
USER appuser
```
- Debian / Azure Linux:
```
GitHub에서 보기