| # Stage 1: Build the React dashboard with Bun. | |
| # The compiled assets are copied into the Go embed directory in stage 2. | |
| FROM oven/bun:1 AS dashboard | |
| WORKDIR /build | |
| COPY dashboard/package.json dashboard/bun.lock ./ | |
| RUN bun install --frozen-lockfile | |
| COPY dashboard/ . | |
| RUN bun run build | |
| # Stage 2: Compile the Go binary. | |
| # Dashboard dist is embedded via Go's embed package. | |
| # Vite always outputs index.html; rename to dashboard.html so it doesn't | |
| # collide with http.FileServer's automatic index.html handling at /dashboard/. | |
| FROM golang:1.26-alpine AS builder | |
| RUN apk add --no-cache git | |
| WORKDIR /build | |
| COPY go.mod go.sum ./ | |
| RUN go mod download | |
| COPY . . | |
| COPY --from=dashboard /build/dist/ internal/dashboard/dashboard/ | |
| RUN mv internal/dashboard/dashboard/index.html internal/dashboard/dashboard/dashboard.html | |
| RUN go build -ldflags="-s -w" -o pinchtab ./cmd/pinchtab | |
| # Stage 3: Minimal runtime image with Chromium. | |
| # Only the compiled binary and entrypoint script are copied in. | |
| # | |
| # Security model: | |
| # - Chrome runs with --no-sandbox (set by entrypoint) because containers don't | |
| # have user namespaces for sandboxing | |
| # - Container provides isolation via cgroups, seccomp, dropped capabilities, | |
| # read-only filesystem, and non-root user | |
| # - This matches best practices for headless Chrome in containerized environments | |
| FROM alpine:3.21 | |
| LABEL org.opencontainers.image.source="https://github.com/pinchtab/pinchtab" | |
| LABEL org.opencontainers.image.description="High-performance browser automation bridge" | |
| # Chromium and its runtime dependencies for headless operation | |
| RUN apk add --no-cache \ | |
| chromium \ | |
| nss \ | |
| freetype \ | |
| harfbuzz \ | |
| ca-certificates \ | |
| ttf-freefont \ | |
| dumb-init | |
| # Non-root user; /data is the persistent volume mount point | |
| RUN adduser -D -h /data -g '' pinchtab && \ | |
| mkdir -p /data && \ | |
| chown pinchtab:pinchtab /data | |
| COPY --from=builder /build/pinchtab /usr/local/bin/pinchtab | |
| COPY --chmod=0755 docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh | |
| USER pinchtab | |
| WORKDIR /data | |
| # HOME and XDG_CONFIG_HOME point into the persistent volume so config | |
| # and Chrome profiles survive container restarts. | |
| ENV HOME=/data \ | |
| XDG_CONFIG_HOME=/data/.config | |
| EXPOSE 7860 | |
| HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ | |
| CMD wget -q -O /dev/null http://localhost:7860/health || exit 1 | |
| ENTRYPOINT ["/usr/bin/dumb-init", "--"] | |
| CMD ["/usr/local/bin/docker-entrypoint.sh", "pinchtab"] | |