# syntax=docker/dockerfile:1.7 # Stage 1: Build frontend FROM node:20-alpine AS frontend-builder WORKDIR /build/web COPY web/package.json web/package-lock.json* ./ RUN npm ci --no-audit COPY web/ ./ RUN npm run build # Stage 2: Build Go binary FROM golang:1.25-alpine AS backend-builder RUN apk add --no-cache git ca-certificates WORKDIR /build COPY go.mod go.sum ./ ENV GOTOOLCHAIN=auto # Cache mounts persist the module + build caches across rebuilds (BuildKit). RUN --mount=type=cache,target=/go/pkg/mod \ go mod download COPY . . # Copy built frontend into the expected embed location. COPY --from=frontend-builder /build/web/build ./web/build RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /tinyforge ./cmd/server # Stage 3: Minimal runtime image FROM alpine:3.19 LABEL org.opencontainers.image.source="https://git.dolgolyov-family.by/alexei.dolgolyov/tiny-forge" LABEL org.opencontainers.image.title="Tinyforge" LABEL org.opencontainers.image.description="Self-hosted Docker deployment + mini-CI platform" RUN apk add --no-cache ca-certificates tzdata wget # Create non-root user. RUN addgroup -g 1000 -S app && adduser -u 1000 -S app -G app WORKDIR /app COPY --from=backend-builder /tinyforge /app/tinyforge # Data directory for SQLite database. RUN mkdir -p /app/data && chown -R app:app /app USER app EXPOSE 8080 ENV DATA_DIR=/app/data ENV LISTEN_ADDR=:8080 VOLUME /app/data # /readyz is the public readiness probe (pings the DB); /livez is liveness. HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/readyz || exit 1 ENTRYPOINT ["/app/tinyforge"]