diff --git a/control-service/Dockerfile b/control-service/Dockerfile index 7d60286..f53b55f 100644 --- a/control-service/Dockerfile +++ b/control-service/Dockerfile @@ -1,15 +1,17 @@ FROM golang:1.26-alpine AS builder -WORKDIR /root -COPY go.mod /root/ -COPY go.sum /root/ +WORKDIR /src +COPY go.mod go.sum ./ RUN go mod download -COPY control-service/* /root/ -COPY internal/ /root/internal/ +COPY control-service/ ./control-service/ +COPY internal/ ./internal/ -RUN CGO_ENABLED=0 GOOS=linux go build -o /app *.go +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /app ./control-service \ + && mkdir -p /runtime/tmp && chmod 1777 /runtime/tmp -FROM alpine:latest -RUN apk --no-cache add ca-certificates -COPY --from=builder /app . -CMD ["/app"] \ No newline at end of file +FROM scratch +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /runtime/tmp /tmp +COPY --from=builder /app /app +ENTRYPOINT ["/app"] diff --git a/file-service/Dockerfile b/file-service/Dockerfile index 6529e66..4d4a754 100644 --- a/file-service/Dockerfile +++ b/file-service/Dockerfile @@ -1,14 +1,17 @@ FROM golang:1.26-alpine AS builder -WORKDIR /root -COPY go.mod /root/ -COPY go.sum /root/ +WORKDIR /src +COPY go.mod go.sum ./ RUN go mod download -COPY file-service/* /root/ -COPY internal/ /root/internal/ -RUN CGO_ENABLED=0 GOOS=linux go build -o /app main.go +COPY file-service/ ./file-service/ +COPY internal/ ./internal/ -FROM alpine:latest -RUN apk --no-cache add ca-certificates -COPY --from=builder /app . -CMD ["/app"] \ No newline at end of file +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /app ./file-service \ + && mkdir -p /runtime/tmp && chmod 1777 /runtime/tmp + +FROM scratch +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /runtime/tmp /tmp +COPY --from=builder /app /app +ENTRYPOINT ["/app"] diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 46d5c80..1ca68a3 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,10 +1,15 @@ -FROM node:24-alpine as client-builder +FROM node:24-alpine AS client-builder WORKDIR /frontend +# Component-test Playwright is unused for the production Vite build; skip its +# browser download so the builder layer stays smaller and faster. +ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 + COPY frontend/package.json /frontend COPY frontend/package-lock.json /frontend -RUN npm ci +RUN npm ci --no-audit --no-fund \ + && npm cache clean --force COPY frontend/vite.config.ts frontend/tsconfig.json frontend/tsconfig.node.json frontend/index.html /frontend/ COPY frontend/public /frontend/public @@ -13,10 +18,27 @@ COPY frontend/src /frontend/src ARG GIT_SHA ENV VITE_GIT_SHA=${GIT_SHA} -RUN npm run build - -FROM caddy:2.10.2-alpine - +RUN npm run build \ + && mkdir -p /caddy-runtime/tmp /caddy-runtime/config /caddy-runtime/data \ + && chmod 1777 /caddy-runtime/tmp + +FROM caddy:2.10.2-alpine AS caddy + +# Official Caddy is a static binary; flatten onto scratch so Alpine (~8MB) and +# unused apk packages never ship. Keep CA certs and mime.types for HTTPS and +# correct static-asset Content-Type headers. +FROM scratch +COPY --from=caddy /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=caddy /etc/mime.types /etc/mime.types +COPY --from=caddy /usr/bin/caddy /usr/bin/caddy +COPY --from=client-builder /caddy-runtime/config /config +COPY --from=client-builder /caddy-runtime/data /data +COPY --from=client-builder /caddy-runtime/tmp /tmp COPY frontend/Caddyfile /etc/caddy/Caddyfile - COPY --from=client-builder /frontend/dist /frontend + +ENV XDG_CONFIG_HOME=/config \ + XDG_DATA_HOME=/data + +EXPOSE 8080 +CMD ["/usr/bin/caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] diff --git a/log-aggregator/Dockerfile b/log-aggregator/Dockerfile index c13ba71..e8e9da0 100644 --- a/log-aggregator/Dockerfile +++ b/log-aggregator/Dockerfile @@ -1,12 +1,16 @@ FROM golang:1.26-alpine AS builder -WORKDIR /root -COPY go.mod go.sum /root/ +WORKDIR /src +COPY go.mod go.sum ./ RUN go mod download -COPY log-aggregator /root/log-aggregator -RUN CGO_ENABLED=0 GOOS=linux go build -o /app ./log-aggregator +COPY log-aggregator/ ./log-aggregator/ -FROM alpine:latest -RUN apk --no-cache add ca-certificates +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /app ./log-aggregator \ + && mkdir -p /runtime/tmp && chmod 1777 /runtime/tmp + +FROM scratch +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /runtime/tmp /tmp COPY --from=builder /app /app -CMD ["/app"] +ENTRYPOINT ["/app"] diff --git a/squid/Dockerfile b/squid/Dockerfile index 3f55b04..b33cb36 100644 --- a/squid/Dockerfile +++ b/squid/Dockerfile @@ -2,7 +2,8 @@ FROM alpine:3.24 EXPOSE 3128 -RUN apk add --no-cache squid +RUN apk add --no-cache squid \ + && rm -rf /usr/share/man /usr/share/doc COPY ./squid/squid.conf /etc/squid/squid.conf COPY ./squid/entrypoint.sh /usr/local/bin/squid-entrypoint diff --git a/worker-csharp/Dockerfile b/worker-csharp/Dockerfile index 8b8b220..ad0c757 100644 --- a/worker-csharp/Dockerfile +++ b/worker-csharp/Dockerfile @@ -1,34 +1,61 @@ ARG PLAYWRIGHT_VERSION=1.60.0 FROM golang:1.26-alpine AS builder -WORKDIR /root -COPY go.mod /root/ -COPY go.sum /root/ +WORKDIR /src +COPY go.mod go.sum ./ RUN go mod download -COPY worker-csharp/main.go /root/ -COPY internal/ /root/internal/ -RUN CGO_ENABLED=0 GOOS=linux go build -o /app +COPY worker-csharp/ ./worker-csharp/ +COPY internal/ ./internal/ +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /app ./worker-csharp -FROM mcr.microsoft.com/playwright/dotnet:v${PLAYWRIGHT_VERSION}-noble +FROM mcr.microsoft.com/playwright/dotnet:v${PLAYWRIGHT_VERSION}-noble AS playwright ARG PLAYWRIGHT_VERSION -ENV PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION -ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 - -RUN apt-get remove -y git ssh xvfb curl && \ - apt-get autoremove -y +ENV PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION \ + DOTNET_CLI_TELEMETRY_OPTOUT=1 \ + DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 \ + DOTNET_NOLOGO=1 \ + NUGET_XMLDOC_MODE=skip \ + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 WORKDIR /home/pwuser/ -USER pwuser +RUN for pkg in git openssh-client xvfb curl wget; do \ + apt-get purge -y "$pkg" || true; \ + done \ + && apt-get autoremove -y --purge \ + && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* /usr/share/man /usr/share/doc /tmp/* -RUN mkdir /home/pwuser/project/ && \ - cd /home/pwuser/project/ && \ - dotnet new console && \ - dotnet add package Microsoft.Playwright --version ${PLAYWRIGHT_VERSION} && \ - dotnet build && \ - rm Program.cs +USER pwuser +RUN mkdir /home/pwuser/project/ \ + && cd /home/pwuser/project/ \ + && dotnet new console \ + && dotnet add package Microsoft.Playwright --version ${PLAYWRIGHT_VERSION} \ + && dotnet build \ + && rm -f Program.cs \ + && dotnet nuget locals http-cache --clear \ + && dotnet nuget locals temp --clear \ + && rm -rf /tmp/* + +FROM scratch +COPY --from=playwright / / COPY --from=builder /app /app -ENTRYPOINT [ "/app" ] \ No newline at end of file +ARG PLAYWRIGHT_VERSION=1.60.0 +ENV PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION \ + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ + PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \ + DOTNET_CLI_TELEMETRY_OPTOUT=1 \ + DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 \ + DOTNET_NOLOGO=1 \ + NUGET_XMLDOC_MODE=skip \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/share/dotnet \ + HOME=/home/pwuser \ + DOTNET_ROOT=/usr/share/dotnet + +USER pwuser +WORKDIR /home/pwuser/ +ENTRYPOINT ["/app"] diff --git a/worker-java/Dockerfile b/worker-java/Dockerfile index dc1b1cd..0d412bd 100644 --- a/worker-java/Dockerfile +++ b/worker-java/Dockerfile @@ -1,21 +1,26 @@ ARG PLAYWRIGHT_VERSION=1.60.0 FROM golang:1.26-alpine AS builder -WORKDIR /root -COPY go.mod /root/ -COPY go.sum /root/ +WORKDIR /src +COPY go.mod go.sum ./ RUN go mod download -COPY worker-java/main.go /root/ -COPY internal/ /root/internal/ -RUN CGO_ENABLED=0 GOOS=linux go build -o /app +COPY worker-java/ ./worker-java/ +COPY internal/ ./internal/ +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /app ./worker-java -FROM mcr.microsoft.com/playwright/java:v$PLAYWRIGHT_VERSION-noble - -RUN apt-get remove -y git ssh xvfb curl && \ - apt-get autoremove -y +FROM mcr.microsoft.com/playwright/java:v${PLAYWRIGHT_VERSION}-noble AS playwright ARG PLAYWRIGHT_VERSION -ENV PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION +ENV PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION \ + MAVEN_OPTS="-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" + +RUN for pkg in git openssh-client xvfb curl wget; do \ + apt-get purge -y "$pkg" || true; \ + done \ + && apt-get autoremove -y --purge \ + && arch="$(dpkg --print-architecture)" \ + && ln -sfn "/usr/lib/jvm/java-25-openjdk-${arch}" /usr/lib/jvm/java-25-openjdk \ + && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* /usr/share/man /usr/share/doc /tmp/* USER pwuser WORKDIR /home/pwuser/ @@ -24,9 +29,23 @@ RUN mkdir /home/pwuser/project/ COPY worker-java/pom.xml /home/pwuser/project/ -RUN cd /home/pwuser/project/ && \ - mvn dependency:resolve-plugins dependency:go-offline +RUN cd /home/pwuser/project/ \ + && mvn -B -DskipTests dependency:resolve-plugins dependency:go-offline \ + && find /home/pwuser/.m2 -type f \( -name '*-sources.jar' -o -name '*-javadoc.jar' \) -delete +FROM scratch +COPY --from=playwright / / COPY --from=builder /app /app -ENTRYPOINT [ "/app" ] +ARG PLAYWRIGHT_VERSION=1.60.0 +ENV PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION \ + PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ + HOME=/home/pwuser \ + JAVA_HOME=/usr/lib/jvm/java-25-openjdk + +USER pwuser +WORKDIR /home/pwuser/ +ENTRYPOINT ["/app"] diff --git a/worker-javascript/Dockerfile b/worker-javascript/Dockerfile index 6dea190..d586c92 100644 --- a/worker-javascript/Dockerfile +++ b/worker-javascript/Dockerfile @@ -1,30 +1,43 @@ ARG PLAYWRIGHT_VERSION=1.60.0 FROM golang:1.26-alpine AS builder -WORKDIR /root -COPY go.mod /root/ -COPY go.sum /root/ +WORKDIR /src +COPY go.mod go.sum ./ RUN go mod download -COPY worker-javascript/main.go /root/ -COPY internal/ /root/internal/ -RUN CGO_ENABLED=0 GOOS=linux go build -o /app +COPY worker-javascript/ ./worker-javascript/ +COPY internal/ ./internal/ +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /app ./worker-javascript -FROM mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-noble +# Debian slim + only the headless browsers the playground actually launches. +# The official mcr.microsoft.com/playwright:noble image is ~3.4GB because it +# ships Ubuntu, headed Chromium, WebKit GTK, headers, and extra tools. +FROM node:24-bookworm-slim ARG PLAYWRIGHT_VERSION -ENV PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION - -RUN apt-get remove -y git ssh xvfb curl && \ - apt-get autoremove -y +ENV PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION \ + PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \ + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ + NPM_CONFIG_UPDATE_NOTIFIER=false \ + NODE_PATH=/usr/local/lib/node_modules \ + DEBIAN_FRONTEND=noninteractive \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 + +RUN useradd --create-home --shell /usr/sbin/nologin pwuser \ + && npm install -g --omit=dev --no-audit --no-fund \ + playwright@${PLAYWRIGHT_VERSION} \ + @playwright/test@${PLAYWRIGHT_VERSION} \ + ts-node \ + && playwright install --with-deps chromium-headless-shell firefox webkit ffmpeg \ + && rm -rf /ms-playwright/webkit-*/minibrowser-gtk \ + && npm cache clean --force \ + && chmod -R 755 /ms-playwright \ + && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* /root/.npm /tmp/* \ + /usr/share/doc /usr/share/man /usr/share/lintian WORKDIR /home/pwuser/ - -RUN npm install -g playwright@${PLAYWRIGHT_VERSION} @playwright/test@${PLAYWRIGHT_VERSION} ts-node - USER pwuser -ENV NODE_PATH=/usr/lib/node_modules - COPY --from=builder /app /app -ENTRYPOINT [ "/app" ] \ No newline at end of file +ENTRYPOINT ["/app"] diff --git a/worker-javascript/main.go b/worker-javascript/main.go index 6bb282a..dbb06f3 100644 --- a/worker-javascript/main.go +++ b/worker-javascript/main.go @@ -18,7 +18,7 @@ func handler(w *worker.Worker, code string) error { if err := os.WriteFile(testPath, []byte(code), 0644); err != nil { return fmt.Errorf("failed to write test file: %w", err) } - return w.ExecCommand("node", "/usr/lib/node_modules/@playwright/test/cli.js", "test", "--trace=on", testPath) + return w.ExecCommand("playwright", "test", "--trace=on", testPath) } if strings.HasSuffix(code, TYPESCRIPT_MAGIC_SUFFIX) { return w.ExecCommand("ts-node", `--compilerOptions='{"isolatedModules": false}'`, "-e", strings.TrimSuffix(code, TYPESCRIPT_MAGIC_SUFFIX)) @@ -28,7 +28,7 @@ func handler(w *worker.Worker, code string) error { func main() { worker.NewWorker(&worker.WorkerExecutionOptions{ - Handler: handler, + Handler: handler, IgnoreFilePatterns: []string{"**/*.last-run.json", "**/.playwright-artifacts-*/**"}, }).Run() } diff --git a/worker-python/Dockerfile b/worker-python/Dockerfile index 4e950f9..4d09e3e 100644 --- a/worker-python/Dockerfile +++ b/worker-python/Dockerfile @@ -1,28 +1,48 @@ ARG PLAYWRIGHT_VERSION=1.60.0 FROM golang:1.26-alpine AS builder -WORKDIR /root -COPY go.mod /root/ -COPY go.sum /root/ +WORKDIR /src +COPY go.mod go.sum ./ RUN go mod download -COPY worker-python/main.go /root/ -COPY internal/ /root/internal/ -RUN CGO_ENABLED=0 GOOS=linux go build -o /app +COPY worker-python/ ./worker-python/ +COPY internal/ ./internal/ +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /app ./worker-python -FROM mcr.microsoft.com/playwright/python:v${PLAYWRIGHT_VERSION}-noble +FROM mcr.microsoft.com/playwright/python:v${PLAYWRIGHT_VERSION}-noble AS playwright ARG PLAYWRIGHT_VERSION -ENV PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION - -RUN apt-get remove -y git ssh xvfb curl && \ - apt-get autoremove -y +ENV PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION \ + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PIP_NO_CACHE_DIR=1 \ + PYTHONDONTWRITEBYTECODE=1 WORKDIR /home/pwuser/ -RUN pip install playwright==${PLAYWRIGHT_VERSION} - -USER pwuser +RUN for pkg in git openssh-client xvfb curl wget; do \ + apt-get purge -y "$pkg" || true; \ + done \ + && apt-get autoremove -y --purge \ + && pip install --no-cache-dir playwright==${PLAYWRIGHT_VERSION} \ + && find /usr/local /usr/lib -type d -name __pycache__ -prune -exec rm -rf {} + \ + && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* /root/.cache /tmp/* +FROM scratch +COPY --from=playwright / / COPY --from=builder /app /app -ENTRYPOINT [ "/app" ] \ No newline at end of file +ARG PLAYWRIGHT_VERSION=1.60.0 +ENV PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION \ + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ + PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PIP_NO_CACHE_DIR=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ + HOME=/home/pwuser + +USER pwuser +WORKDIR /home/pwuser/ +ENTRYPOINT ["/app"]