# syntax=docker/dockerfile:1.7 # ============================================================================== # Stage 1: Build Frontend # ============================================================================== FROM node:20-alpine AS frontend-builder WORKDIR /frontend # Install deps without running prepare/postinstall scripts. # svelte-kit sync needs the full source tree (svelte.config.js etc.) to work, # which we don't have yet at this layer. COPY web/frontend/package*.json ./ RUN --mount=type=cache,target=/root/.npm \ npm ci --include=dev --ignore-scripts # Bring in the rest of the source, including svelte.config.js. COPY web/frontend/ ./ # Run svelte-kit sync explicitly now that the config is present. RUN npx svelte-kit sync RUN --mount=type=cache,target=/root/.npm \ npm run build # Output: /frontend/build/ contains production static files (via adapter-static) # ============================================================================== # Stage 2: Build Go Binary # ============================================================================== FROM golang:1.27-alpine AS go-builder WORKDIR /build # Install build dependencies RUN apk add --no-cache \ git \ ca-certificates \ build-base \ sqlite-dev # Copy go module files and download dependencies (cached layer) COPY go.mod go.sum ./ RUN --mount=type=cache,target=/go/pkg/mod \ go mod download # Copy application source COPY . . # Copy built frontend from stage 1 # SvelteKit with adapter-static outputs to build/ COPY --from=frontend-builder /frontend/build ./web/dist # Build binary with optimizations and version injection # CGO_ENABLED=1 required for SQLite ARG VERSION=dev ARG COMMIT=unknown ARG BUILD_DATE=unknown RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=1 GOOS=linux \ CGO_CFLAGS="-D_LARGEFILE64_SOURCE" \ go build \ -tags sqlite_omit_load_extension \ -ldflags="-w -s \ -X github.com/javinizer/javinizer-go/internal/version.Version=${VERSION} \ -X github.com/javinizer/javinizer-go/internal/version.Commit=${COMMIT} \ -X github.com/javinizer/javinizer-go/internal/version.BuildDate=${BUILD_DATE}" \ -o javinizer \ ./cmd/javinizer # ============================================================================== # Stage 3: Runtime # ============================================================================== FROM alpine:3.21 ARG VERSION=dev ARG COMMIT=unknown ARG BUILD_DATE=unknown LABEL maintainer="javinizer@example.com" \ description="JAV metadata scraper and organizer" \ org.opencontainers.image.title="Javinizer" \ org.opencontainers.image.description="JAV metadata scraper and organizer" \ org.opencontainers.image.source="https://github.com/javinizer/javinizer-go" \ org.opencontainers.image.version="${VERSION}" \ org.opencontainers.image.revision="${COMMIT}" \ org.opencontainers.image.created="${BUILD_DATE}" \ version="${VERSION}" # Working directory is now /javinizer (app state location) WORKDIR /javinizer # Install runtime dependencies including Chromium for browser automation RUN apk add --no-cache \ ca-certificates \ tzdata \ sqlite \ su-exec \ wget \ chromium \ nss \ freetype \ harfbuzz \ ttf-freefont # Preserve image defaults for runtime UID/GID selection. The entrypoint still # prefers PUID/PGID, then falls back to USER_ID/GROUP_ID, and only uses these # values when no runtime override is provided. ARG USER_ID=1000 ARG GROUP_ID=1000 # Copy binary to /usr/local/bin for system-wide access COPY --from=go-builder /build/javinizer /usr/local/bin/javinizer RUN chmod +x /usr/local/bin/javinizer # Copy frontend static files COPY --from=go-builder /build/web/dist /app/web/dist # Swagger/OpenAPI docs are embedded in the binary via go:embed (docs/swagger/embed.go) # Copy entrypoint script COPY scripts/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh # Create image-managed state directories. Sticky world-writable permissions are # only a fallback for containers started with an explicit Docker `user:` that # bypasses the root bootstrap; mounted volumes still rely on host ownership. RUN mkdir -p /javinizer/logs /javinizer/cache /javinizer/temp /media && \ chmod 1777 /javinizer /javinizer/logs /javinizer/cache /javinizer/temp /media # Environment variables ENV JAVINIZER_HOME=/javinizer \ JAVINIZER_CONFIG=/javinizer/config.yaml \ JAVINIZER_DB=/javinizer/javinizer.db \ JAVINIZER_LOG_DIR=/javinizer/logs \ JAVINIZER_TEMP_DIR=/javinizer/temp \ JAVINIZER_INIT_SERVER_HOST=0.0.0.0 \ JAVINIZER_INIT_ALLOWED_DIRECTORIES=/media \ JAVINIZER_INIT_ALLOWED_ORIGINS="http://localhost:8765,http://localhost:5173,http://localhost:5174,http://127.0.0.1:8765,http://127.0.0.1:5173,http://127.0.0.1:5174" \ JAVINIZER_IMAGE_DEFAULT_UID=${USER_ID} \ JAVINIZER_IMAGE_DEFAULT_GID=${GROUP_ID} \ CHROME_BIN=/usr/bin/chromium-browser \ CHROME_PATH=/usr/bin/chromium-browser \ XDG_CONFIG_HOME=/tmp/.chromium \ XDG_CACHE_HOME=/tmp/.chromium \ PATH="/usr/local/bin:${PATH}" # Expose API/web port EXPOSE 8765 # Health check endpoint HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD wget --no-verbose --tries=1 --method=GET -O /dev/null http://localhost:8765/health || exit 1 # Entrypoint script to initialize config ENTRYPOINT ["docker-entrypoint.sh"] # Run web server (will be passed to entrypoint) CMD ["javinizer", "web"]