# Secure Production Dockerfile with cache optimization # Build with: DOCKER_BUILDKIT=1 docker build --build-arg WORKER_PASSWORD=$(openssl rand -base64 32) -f build/docker/secure-prod.Dockerfile . # ============================================================================ # STAGE 1: Go Dependencies (cached layer) # ============================================================================ FROM golang:1.25-alpine AS go-deps RUN apk add --no-cache git make gcc musl-dev WORKDIR /app # Copy only module files first for maximum cache efficiency COPY go.mod go.sum ./ RUN --mount=type=cache,target=/go/pkg/mod \ go mod download && \ go mod verify # ============================================================================ # STAGE 2: Go Builder # ============================================================================ FROM go-deps AS go-builder # Copy source code (changes here won't rebuild deps layer) COPY cmd/ ./cmd/ COPY internal/ ./internal/ COPY pkg/ ./pkg/ COPY tools/ ./tools/ # Build Go binaries with cache mount for build cache RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=1 go build -ldflags="-w -s" -o bin/api-server ./cmd/api-server/main.go && \ CGO_ENABLED=1 go build -ldflags="-w -s" -o bin/worker ./cmd/worker # ============================================================================ # STAGE 3: Final Runtime with Podman and secure SSH # ============================================================================ FROM alpine:3.19 # Build argument for worker password (no hardcoded secrets) ARG WORKER_PASSWORD=changeme ENV WORKER_PASSWORD=$WORKER_PASSWORD # Create app user and worker user, configure SSH in combined layer RUN addgroup -g 1001 -S appgroup && \ adduser -u 1001 -S appuser -G appgroup && \ addgroup -g 1002 -S workergroup && \ adduser -u 1002 -S worker -G workergroup -s /bin/sh && \ echo "worker:$WORKER_PASSWORD" | chpasswd && \ mkdir -p /home/worker/.ssh && \ chown -R worker:workergroup /home/worker && \ \ # Generate SSH keys for worker user ssh-keygen -t rsa -b 4096 -f /home/worker/.ssh/id_rsa -N "" && \ cp /home/worker/.ssh/id_rsa.pub /home/worker/.ssh/authorized_keys && \ chmod 700 /home/worker/.ssh && \ chmod 600 /home/worker/.ssh/id_rsa && \ chmod 644 /home/worker/.ssh/id_rsa.pub /home/worker/.ssh/authorized_keys && \ chown -R worker:workergroup /home/worker/.ssh && \ \ # Configure SSH daemon securely echo "Port 2222" >> /etc/ssh/sshd_config && \ echo "PermitRootLogin no" >> /etc/ssh/sshd_config && \ echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config && \ echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config && \ echo "AuthorizedKeysFile %h/.ssh/authorized_keys" >> /etc/ssh/sshd_config && \ echo "AllowUsers worker" >> /etc/ssh/sshd_config && \ echo "MaxAuthTries 3" >> /etc/ssh/sshd_config && \ echo "ClientAliveInterval 300" >> /etc/ssh/sshd_config && \ echo "ClientAliveCountMax 2" >> /etc/ssh/sshd_config && \ echo "X11Forwarding no" >> /etc/ssh/sshd_config && \ echo "AllowTcpForwarding no" >> /etc/ssh/sshd_config && \ echo "Banner /etc/ssh/banner" >> /etc/ssh/sshd_config && \ \ # Create SSH banner echo "=================================================" > /etc/ssh/banner && \ echo " ML Experiments Production Server" >> /etc/ssh/banner && \ echo " Unauthorized access is prohibited" >> /etc/ssh/banner && \ echo "=================================================" >> /etc/ssh/banner && \ \ # Generate SSH host keys ssh-keygen -A && \ \ # Give appuser sudo permissions for SSH and worker user for Podman echo "appuser ALL=(ALL) NOPASSWD: /usr/sbin/sshd" >> /etc/sudoers && \ echo "worker ALL=(ALL) NOPASSWD: /usr/bin/podman" >> /etc/sudoers # Switch to app user for application USER appuser # Expose ports EXPOSE 9101 2222 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -k -f https://localhost:9101/health || exit 1 # Default command for API server CMD ["/usr/local/bin/api-server", "-config", "/app/configs/api/prod.yaml"]