File size: 2,452 Bytes
6a7089a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# 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"]