Files
kuber/lib/scaffold-templates.ts
2026-10-06 15:31:51 +00:00

263 lines
6.1 KiB
TypeScript

/** 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,
};
}