/** Embedded templates are bundled with the CLI; no files are read at runtime. */ export const templateIds = [ "bun-service", "bun-next", "bun-compiled", "node-pnpm", "python-web", "go-static", "rust-static", "static-nginx", ] as const; export type DockerfileTemplateId = (typeof templateIds)[number]; export type DockerfileTemplate = { id: DockerfileTemplateId; label: string; description: string; }; export type RenderedDockerfileTemplate = { dockerfile: string; dockerignore: string; }; const commonIgnore = `# Local state and credentials must not enter the build context. .git .github .env .env.* **/.env **/.env.* *.pem *.key *.p12 id_rsa* credentials.json *.log .DS_Store `; const templates: Record< DockerfileTemplateId, DockerfileTemplate & RenderedDockerfileTemplate > = { "bun-service": { id: "bun-service", label: "Bun service", description: "Bun app with package.json and bun.lock; starts src/index.ts on port 3000.", dockerfile: `FROM oven/bun:1-alpine AS deps WORKDIR /app COPY package.json bun.lock ./ RUN bun install --frozen-lockfile --production FROM oven/bun:1-alpine WORKDIR /app ENV NODE_ENV=production COPY --from=deps /app/node_modules ./node_modules COPY package.json ./ COPY src ./src USER bun EXPOSE 3000 CMD ["bun", "src/index.ts"] `, dockerignore: `${commonIgnore}node_modules dist .next coverage `, }, "bun-next": { id: "bun-next", label: "Bun / Next.js standalone", description: "Next.js app with bun.lock and output: 'standalone' in next.config; starts generated server.js on port 3000.", dockerfile: `FROM oven/bun:1-alpine AS build WORKDIR /app COPY package.json bun.lock ./ RUN bun install --frozen-lockfile COPY . . RUN mkdir -p public && bun run build FROM oven/bun:1-alpine WORKDIR /app ENV NODE_ENV=production HOSTNAME=0.0.0.0 PORT=3000 COPY --from=build /app/.next/standalone ./ COPY --from=build /app/.next/static ./.next/static COPY --from=build /app/public ./public USER bun EXPOSE 3000 CMD ["bun", "server.js"] `, dockerignore: `${commonIgnore}node_modules .next dist coverage `, }, "bun-compiled": { id: "bun-compiled", label: "Compiled Bun executable", description: "Bun app with bun.lock and src/index.ts; compiles a Linux executable named app.", dockerfile: `FROM oven/bun:1 AS build WORKDIR /app COPY package.json bun.lock ./ RUN bun install --frozen-lockfile COPY . . RUN bun build src/index.ts --compile --outfile /app/app FROM oven/bun:1-slim WORKDIR /app COPY --from=build /app/app ./app USER bun EXPOSE 3000 CMD ["./app"] `, dockerignore: `${commonIgnore}node_modules dist .next coverage `, }, "node-pnpm": { id: "node-pnpm", label: "Node / pnpm service", description: "Node app with pnpm-lock.yaml, a build script producing dist/index.js, and a packageManager pin in package.json.", dockerfile: `FROM node:22-alpine AS build WORKDIR /app RUN corepack enable COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile COPY . . RUN pnpm run build && pnpm prune --prod FROM node:22-alpine WORKDIR /app ENV NODE_ENV=production COPY --from=build /app/package.json ./ COPY --from=build /app/node_modules ./node_modules COPY --from=build /app/dist ./dist USER node EXPOSE 3000 CMD ["node", "dist/index.js"] `, dockerignore: `${commonIgnore}node_modules dist coverage .next `, }, "python-web": { id: "python-web", label: "Python web service", description: "Python app with requirements.txt and an ASGI app object at app:app; serves on port 8000 via uvicorn (include it in requirements.txt).", dockerfile: `FROM python:3.12-slim AS build WORKDIR /app RUN python -m venv /opt/venv ENV PATH=/opt/venv/bin:$PATH COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt FROM python:3.12-slim WORKDIR /app ENV PATH=/opt/venv/bin:$PATH PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 COPY --from=build /opt/venv /opt/venv COPY . . USER 10001:10001 EXPOSE 8000 CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"] `, dockerignore: `${commonIgnore}__pycache__/ *.py[cod] .venv/ venv/ .pytest_cache/ dist/ `, }, "go-static": { id: "go-static", label: "Static Go binary", description: "Go module with go.mod and a main package at module root; builds a CGO-free app binary on port 8080.", dockerfile: `FROM golang:1.24-alpine AS build WORKDIR /src COPY go.mod go.sum* ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -trimpath -o /app . FROM scratch COPY --from=build /app /app COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ USER 65532:65532 EXPOSE 8080 ENTRYPOINT ["/app"] `, dockerignore: `${commonIgnore}vendor/ bin/ coverage/ `, }, "rust-static": { id: "rust-static", label: "Static Rust binary", description: "Cargo project with Cargo.lock and a binary named app (src/main.rs); no dynamic native-library dependencies; serves on port 8080.", dockerfile: `FROM rust:1-alpine AS build WORKDIR /app RUN apk add --no-cache musl-dev COPY Cargo.toml Cargo.lock ./ COPY src ./src RUN cargo build --release --locked --bin app FROM scratch COPY --from=build /app/target/release/app /server USER 65532:65532 EXPOSE 8080 ENTRYPOINT ["/server"] `, dockerignore: `${commonIgnore}target/ coverage/ `, }, "static-nginx": { id: "static-nginx", label: "Static site / nginx", description: "Static site with ready-to-serve files in dist/; nginx serves them on port 80.", dockerfile: `FROM nginx:1-alpine COPY dist/ /usr/share/nginx/html/ EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] `, dockerignore: `${commonIgnore}node_modules .next coverage `, }, }; export function listDockerfileTemplates(): DockerfileTemplate[] { return templateIds.map((id) => { const { label, description } = templates[id]; return { id, label, description }; }); } export function renderDockerfileTemplate( id: DockerfileTemplateId, ): RenderedDockerfileTemplate { const template = templates[id]; if (!template) throw new Error(`Unknown Dockerfile template: ${id}`); return { dockerfile: template.dockerfile, dockerignore: template.dockerignore, }; }