Docker Image Creation
Generate production-ready Dockerfiles by analyzing language, framework, and dependencies. Output is a multi-stage build optimized for small image size, fast builds, and minimal attack surface.
Default base image strategy: distroless when possible (only the app and its runtime — no shell, no package manager, no OS utilities; minimal CVE exposure). Fall back to slim (node:22-slim, python:3.12-slim) when the runtime needs OS-level libraries or a shell for debugging.
Workflow
Step 1: Detect the project
Scan the project root to identify:
Language + version (from version files / configs):
package.json(Node.js —engines.node)go.mod(Go —godirective)requirements.txt/pyproject.toml/Pipfile(Python —python_requires/.python-version)pom.xml/build.gradle{,kts}(Java/Kotlin —sourceCompatibility)*.csproj/*.fsproj(C#/F# —TargetFramework)mix.exs(Elixir),Gemfile(Ruby)
Framework (drives build command + output structure):
- Node.js: Next.js, Remix, Express, Fastify, NestJS, Astro, SvelteKit
- Python: FastAPI, Flask, Django
- Go: net/http, Gin, Echo, Fiber
- Java: Spring Boot, Quarkus, Micronaut
- .NET: ASP.NET Core, Blazor, gRPC, minimal APIs
Package manager (drives install command):
- Node.js: npm (
package-lock.json), yarn (yarn.lock), pnpm (pnpm-lock.yaml), bun (bun.lockb) - Python: pip (
requirements.txt), poetry (poetry.lock), pipenv (Pipfile.lock), uv (uv.lock)
- Node.js: npm (
Entry point:
package.jsonscripts.start/main,Procfile, existing DockerfileCMD, or framework convention (main.go,app.py,Application.java).Existing Docker artifacts:
.dockerignore, existingDockerfile,docker-compose.yml.
Present a detection summary to the user and ask: "Want me to adjust anything before generating?"
Step 2: Ask about registry and build tools
Ask:
- Container registry? (Docker Hub, ghcr.io, ECR, GCR, ACR) — affects
FROMpaths and login steps. - Build tool? Default: BuildKit (
DOCKER_BUILDKIT=1). Enables cache mounts, secret mounts, and parallel stages. - Generate
.dockerignore? If missing or sparse. - Generate
docker-compose.yml? For local development.
Proceed with sensible defaults if the user says "just go with defaults."
Step 3: Generate the Dockerfile
Layer order (least-changed → most-changed) for cache efficiency: base image → system deps → dependency manifest + install → source code → build → final stage with only the runtime artifact.
Cache-friendly install — copy manifests first, install, then copy source:
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
Never COPY . . before install — busts the cache on every source change.
Stages: deps (install) → build (compile/bundle) → runtime (minimal base with artifact only). Two stages suffice for interpreted languages without a build step.
Base image table:
| Language | Build stage | Runtime stage |
|---|---|---|
| Go | golang:<version> |
gcr.io/distroless/static-debian12 |
| Java | eclipse-temurin:<version> |
gcr.io/distroless/java21-debian12 |
| Node.js | node:<version> |
node:<version>-slim or gcr.io/distroless/nodejs22-debian12 |
| Python | python:<version> |
python:<version>-slim |
| .NET | mcr.microsoft.com/dotnet/sdk:<version> |
mcr.microsoft.com/dotnet/aspnet:<version>-alpine |
Pin major.minor (e.g., node:22.12, not node:latest). Digest-pin only for high-security environments.
Security defaults:
- Run as non-root in the final stage (
USER nonrootfor distroless; create a user for slim bases) - Never copy
.env, secrets, or credentials into the image - Use
--frozen-lockfile/--cifor reproducible installs - Set
NODE_ENV=productionor equivalent - Use
COPY --chownto set ownership without running as root
Health checks (when the app exposes HTTP):
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD ["wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] || exit 1
Distroless images lack wget/curl — skip HEALTHCHECK and rely on the orchestrator (Kubernetes liveness probes etc.).
OCI labels:
LABEL org.opencontainers.image.source="https://github.com/OWNER/REPO"
LABEL org.opencontainers.image.description="Brief description"
Step 4: Generate .dockerignore
If absent or sparse, write one tailored to the detected language. Common patterns:
.git
.github
node_modules
dist
build
*.md
.env*
.vscode
.idea
Dockerfile*
docker-compose*
.dockerignore
coverage
.nyc_output
__pycache__
*.pyc
.pytest_cache
.mypy_cache
bin/
obj/
Drop language-irrelevant patterns (no Python patterns for a Go project).
Step 5: Generate docker-compose.yml (if requested)
Development-focused: volume mounts for live reload, port mappings, env-file references, dependent services (database, cache) if detected.
Step 6: Provide build and run instructions
docker build -t my-app .
docker run -p 3000:3000 my-app
# With BuildKit
DOCKER_BUILDKIT=1 docker build -t my-app .
Include framework-specific notes (e.g., Next.js standalone mode requires output: 'standalone' in next.config.js).
Language-specific patterns
Node.js / Next.js (standalone)
--frozen-lockfilefor reproducible installs- For Next.js: enable standalone output and copy
.next/standalone+.next/static+public/ - Set
NODE_ENV=productionbefore the build step (for tree-shaking) - Use the
nodeuser (built-in to slim images)
Go
CGO_ENABLED=0for fully static binaries (works with distroless/static)- Copy only the binary to the final stage
- Use scratch or distroless/static — Go binaries are self-contained
- Strip debug info:
GOFLAGS="-trimpath"+-ldflags="-s -w"
Python
--no-cache-dirwith pip (don't bake wheels into the image)- Poetry: export to requirements.txt in the build stage, pip-install in runtime
--no-install-recommendsforapt-get- Consider
uvfor faster installs
C# / .NET
- Build stage
mcr.microsoft.com/dotnet/sdk:<version>; runtimemcr.microsoft.com/dotnet/aspnet:<version>-alpine - Restore first for cache efficiency:
COPY *.csproj ./→dotnet restore→ copy source →dotnet publish dotnet publish -c Release -o /app --no-restore(skip redundant restore)- Self-contained:
dotnet publish -c Release --self-contained -r linux-musl-x64 -p:PublishTrimmed=true - Globalization invariant if no culture-specific formatting:
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true(saves ~30MB) - Multi-project solutions: copy all
.csprojpreserving structure, restore, copy everything, publish entry-point project USER app(built-in non-root in .NET 8+),ASPNETCORE_URLS=http://+:8080(default for non-root)
Java (Spring Boot)
- Layered jars:
java -Djarmode=layertools -jar app.jar extract; copy dependencies, spring-boot-loader, snapshot-dependencies, application as separate layers jlinkfor custom minimal JRE-XX:+UseContainerSupportfor correct memory detection in containers