# ============================================================================= # Sub2API Multi-Stage Dockerfile # ============================================================================= # Stage 1: Build frontend # Stage 2: Build Go backend with embedded frontend # Stage 3: Final minimal image # ============================================================================= ARG NODE_IMAGE=node:24-alpine ARG GOLANG_IMAGE=golang:1.26.1-alpine ARG PYTHON_IMAGE=python:3.12-alpine3.20 ARG GOPROXY=https://goproxy.cn,direct ARG GOSUMDB=sum.golang.google.cn # ----------------------------------------------------------------------------- # Stage 1: Frontend Builder # ----------------------------------------------------------------------------- FROM ${NODE_IMAGE} AS frontend-builder WORKDIR /app/frontend # Install pnpm RUN corepack enable && corepack prepare pnpm@latest --activate # Install dependencies first (better caching) COPY frontend/package.json frontend/pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile # Copy frontend source and build COPY frontend/ ./ RUN pnpm run build # ----------------------------------------------------------------------------- # Stage 2: Backend Builder # ----------------------------------------------------------------------------- FROM ${GOLANG_IMAGE} AS backend-builder # Build arguments for version info (set by CI) ARG VERSION= ARG COMMIT=docker ARG DATE ARG GOPROXY ARG GOSUMDB ENV GOPROXY=${GOPROXY} ENV GOSUMDB=${GOSUMDB} # Install build dependencies RUN apk add --no-cache git ca-certificates tzdata WORKDIR /app/backend # Copy go mod files first (better caching) COPY backend/go.mod backend/go.sum ./ RUN go mod download # Copy backend source first COPY backend/ ./ # Copy frontend dist from previous stage (must be after backend copy to avoid being overwritten) COPY --from=frontend-builder /app/backend/internal/web/dist ./internal/web/dist # Build the binary (BuildType=release for CI builds, embed frontend) # Version precedence: build arg VERSION > cmd/server/VERSION RUN VERSION_VALUE="${VERSION}" && \ if [ -z "${VERSION_VALUE}" ]; then VERSION_VALUE="$(tr -d '\r\n' < ./cmd/server/VERSION)"; fi && \ DATE_VALUE="${DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" && \ CGO_ENABLED=0 GOOS=linux go build \ -tags embed \ -ldflags="-s -w -X main.Version=${VERSION_VALUE} -X main.Commit=${COMMIT} -X main.Date=${DATE_VALUE} -X main.BuildType=release" \ -trimpath \ -o /app/sub2api \ ./cmd/server # ----------------------------------------------------------------------------- # Stage 3: Final Runtime Image # ----------------------------------------------------------------------------- FROM ${PYTHON_IMAGE} # Labels LABEL maintainer="Wei-Shaw " LABEL description="Sub2API - AI API Gateway Platform" LABEL org.opencontainers.image.source="https://github.com/Wei-Shaw/sub2api" ENV PATH="/usr/lib/postgresql16/bin:${PATH}" \ PIP_DISABLE_PIP_VERSION_CHECK=1 \ PYTHONDONTWRITEBYTECODE=1 # Install runtime dependencies RUN apk add --no-cache \ ca-certificates \ tzdata \ curl \ su-exec \ bash \ redis \ postgresql16 \ postgresql16-client \ wget \ && rm -rf /var/cache/apk/* # Create non-root user RUN addgroup -g 1000 sub2api && \ adduser -u 1000 -G sub2api -s /bin/sh -D sub2api # Set working directory WORKDIR /app # Copy binary/resources with ownership to avoid extra full-layer chown copy COPY --from=backend-builder --chown=sub2api:sub2api /app/sub2api /app/sub2api COPY --from=backend-builder --chown=sub2api:sub2api /app/backend/resources /app/resources # Install Hugging Face backup helper dependencies COPY deploy/huggingface/requirements.txt /tmp/hf-requirements.txt RUN pip install --no-cache-dir -r /tmp/hf-requirements.txt && rm -f /tmp/hf-requirements.txt # Copy deployment helpers COPY deploy/huggingface/ /app/deploy/huggingface/ # Create data directory RUN mkdir -p /app/data /data && \ chmod +x /app/deploy/huggingface/start.sh /app/deploy/huggingface/backup.sh && \ chown -R sub2api:sub2api /app /data # Expose port (can be overridden by SERVER_PORT env var) EXPOSE 8080 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ CMD wget -q -T 5 -O /dev/null http://localhost:${SERVER_PORT:-8080}/health || exit 1 # Run the Hugging Face startup orchestrator ENTRYPOINT ["/app/deploy/huggingface/start.sh"]