@@ -5,19 +5,142 @@ export default function dockerize(
55) {
66 let dockerfile = "" ;
77 build_cmds = build_cmds . replace ( / \r ? \n $ / , '' ) ;
8- const run_cmd = build_cmds . split ( "\n" ) ;
9- const execute_cmd = "CMD " + JSON . stringify ( run_cmd . pop ( ) ?. split ( " " ) ) ;
10- const build_cmds_mapped = run_cmd . map ( ( elem ) => {
11- return "RUN " + elem ;
12- } ) . join ( "\n" ) ;
8+ const run_cmd = build_cmds . split ( "\n" ) . map ( c => c . trim ( ) ) . filter ( Boolean ) ;
9+ const last_cmd = run_cmd . pop ( ) ;
10+ if ( ! last_cmd ) {
11+ throw new Error ( "build_cmds must contain at least one valid execution command" ) ;
12+ }
13+ let execute_cmd = "CMD " + JSON . stringify ( last_cmd . split ( " " ) ) ;
14+ let build_steps = run_cmd . filter ( Boolean ) . map ( ( cmd ) => `RUN ${ cmd } ` ) ;
1315 if ( stack == "Python" ) {
14- dockerfile =
15- "FROM python:latest \nWORKDIR /app \nCOPY requirements.txt . \nRUN pip install --no-cache-dir -r requirements.txt \nCOPY . ." +
16- build_cmds_mapped + `\nEXPOSE ${ port } \n` + execute_cmd ;
16+ dockerfile = [
17+ "FROM python:3.12-slim AS builder" ,
18+ "WORKDIR /app" ,
19+ "RUN python -m venv /opt/venv" ,
20+ "ENV PATH=\"/opt/venv/bin:$PATH\"" ,
21+ "COPY . ." ,
22+ "RUN if [ -f requirements.txt ]; then pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir -r requirements.txt; fi" ,
23+ ...build_steps ,
24+ "" ,
25+ "FROM python:3.12-slim" ,
26+ "RUN groupadd -r appuser && useradd -r -g appuser appuser" ,
27+ "WORKDIR /app" ,
28+ "COPY --from=builder /opt/venv /opt/venv" ,
29+ "COPY --from=builder /app /app" ,
30+ "ENV PATH=\"/opt/venv/bin:$PATH\"" ,
31+ "USER appuser" ,
32+ `EXPOSE ${ port } ` ,
33+ execute_cmd ,
34+ ] . join ( "\n" ) ;
1735 } else if ( stack == "NodeJS" ) {
18- dockerfile =
19- "FROM node:latest \nWORKDIR /app \nCOPY ./package*.json . \nRUN npm install \nCOPY . ." +
20- build_cmds_mapped + `\nEXPOSE ${ port } \n` + execute_cmd ;
36+ dockerfile = [
37+ "FROM node:22-alpine AS builder" ,
38+ "WORKDIR /app" ,
39+ "COPY . ." ,
40+ "RUN if [ -f package.json ]; then npm install && npm cache clean --force; fi" ,
41+ ...build_steps ,
42+ "RUN rm -rf node_modules" ,
43+ "" ,
44+ "FROM node:22-alpine" ,
45+ "WORKDIR /app" ,
46+ "ENV NODE_ENV=production" ,
47+ "COPY --from=builder /app ./" ,
48+ "RUN if [ -f package.json ]; then npm install --omit=dev && npm cache clean --force; fi" ,
49+ "USER node" ,
50+ `EXPOSE ${ port } ` ,
51+ execute_cmd ,
52+ ] . join ( "\n" ) ;
53+ } else if ( stack === "Go" ) {
54+ let goBuildOverride : string [ ] = [ ] ;
55+ if ( last_cmd . startsWith ( "go run" ) ) {
56+ const target = last_cmd . replace ( "go run " , "" ) ;
57+ goBuildOverride = [ `RUN go build -o app_binary ${ target } ` ] ;
58+ execute_cmd = 'CMD ["./app_binary"]' ;
59+ }
60+ dockerfile = [
61+ "FROM golang:1.22-alpine AS builder" ,
62+ "WORKDIR /app" ,
63+ "COPY . ." ,
64+ "RUN if [ -f go.mod ]; then go mod download; fi" ,
65+ ...build_steps ,
66+ ...goBuildOverride ,
67+ "RUN find . -type f ! -executable -delete && rm -rf vendor/ .git/ *.go go.*" ,
68+ "" ,
69+ "FROM alpine:3.19" ,
70+ "RUN addgroup -S appgroup && adduser -S appuser -G appgroup" ,
71+ "RUN apk add --no-cache ca-certificates" ,
72+ "WORKDIR /app" ,
73+ "COPY --from=builder /app /app" ,
74+ "USER appuser" ,
75+ `EXPOSE ${ port } ` ,
76+ execute_cmd ,
77+ ] . join ( "\n" ) ;
78+ } else if ( stack === "Rust" ) {
79+ let rustBuildOverride : string [ ] = [ ] ;
80+ let processed_build_steps = build_steps ;
81+
82+ if ( last_cmd . startsWith ( "cargo run" ) ) {
83+ processed_build_steps = build_steps . filter ( step => ! step . includes ( "cargo build" ) ) ;
84+ rustBuildOverride = [ `RUN cargo build --release && find target/release -maxdepth 1 -type f -executable -exec mv {} ./app_binary \\;` ] ;
85+ execute_cmd = 'CMD ["./app_binary"]' ;
86+ } else if ( last_cmd . startsWith ( "rustc " ) ) {
87+ const target = last_cmd . replace ( "rustc " , "" ) ;
88+ processed_build_steps = build_steps . filter ( step => ! step . includes ( "rustc " ) ) ;
89+ rustBuildOverride = [ `RUN rustc ${ target } -o app_binary` ] ;
90+ execute_cmd = 'CMD ["./app_binary"]' ;
91+ }
92+
93+ dockerfile = [
94+ "FROM rust:alpine AS builder" ,
95+ "RUN apk add --no-cache musl-dev" ,
96+ "WORKDIR /app" ,
97+ "COPY . ." ,
98+ ...processed_build_steps ,
99+ ...rustBuildOverride ,
100+ "# Generator limitation: Since we don't know the exact binary name, we delete all source files and keep only the compiled executables" ,
101+ "RUN find . -type f ! -executable -delete && rm -rf src/ .git/ Cargo.* vendor/ target/" ,
102+ "" ,
103+ "FROM alpine:3.19" ,
104+ "RUN addgroup -S appgroup && adduser -S appuser -G appgroup" ,
105+ "RUN apk add --no-cache ca-certificates libgcc" ,
106+ "WORKDIR /app" ,
107+ "COPY --from=builder /app /app" ,
108+ "USER appuser" ,
109+ `EXPOSE ${ port } ` ,
110+ execute_cmd ,
111+ ] . join ( "\n" ) ;
112+ } else if ( stack === "React" ) {
113+ dockerfile = [
114+ "FROM node:22-alpine AS builder" ,
115+ "WORKDIR /app" ,
116+ "COPY . ." ,
117+ "RUN if [ -f package.json ]; then npm install && npm cache clean --force; fi" ,
118+ ...run_cmd . map ( ( cmd ) => `RUN ${ cmd } ` ) ,
119+ `RUN ${ last_cmd } ` ,
120+ "RUN if [ -d \"build\" ]; then mv build /app_output; elif [ -d \"dist\" ]; then mv dist /app_output; else echo \"No build or dist folder found!\" && exit 1; fi" ,
121+ "" ,
122+ "FROM nginx:alpine" ,
123+ "COPY --from=builder /app_output /usr/share/nginx/html" ,
124+ `RUN printf "server {\\n listen ${ port } ;\\n location / {\\n root /usr/share/nginx/html;\\n index index.html index.htm;\\n try_files \\$uri \\$uri/ /index.html;\\n }\\n}" > /etc/nginx/conf.d/default.conf` ,
125+ `EXPOSE ${ port } ` ,
126+ ] . join ( "\n" ) ;
21127 }
22128 return dockerfile . toString ( ) ;
23129}
130+
131+ export function dockerignore ( stack : string ) : string {
132+ const common = [
133+ ".git" , ".gitignore" , ".dockerignore" ,
134+ "*.md" , ".DS_Store" ,
135+ ] ;
136+
137+ const stackRules : Record < string , string [ ] > = {
138+ Python : [ "__pycache__/" , "*.pyc" , "*.pyo" , ".venv/" , "dist/" , "*.egg-info/" ] ,
139+ NodeJS : [ "node_modules/" , "dist/" , ".npm/" , "*.log" , "coverage/" ] ,
140+ Go : [ "bin/" , "obj/" , "*.exe" , "*.dll" , "*.so" , "*.dylib" ] ,
141+ Rust : [ "target/" , "**/*.rs.bk" ] ,
142+ React : [ "node_modules/" , "build/" , "dist/" , ".npm/" , "*.log" , "coverage/" ] ,
143+ } ;
144+
145+ return [ ...common , ...( stackRules [ stack ] ?? [ ] ) ] . join ( "\n" ) + "\n" ;
146+ }
0 commit comments