refactor: Switch Docker builds to scratch base images and non-root users, improving security and reducing image size.

This commit is contained in:
Alex Peters 2026-02-17 10:43:18 +01:00
parent 1ef33c90ed
commit 2ea84ceae5
No known key found for this signature in database
GPG key ID: 52E47A1C1FE6CFF3
4 changed files with 59 additions and 22 deletions

View file

@ -1,5 +1,6 @@
.git
.gitignore
.github/
build/
.picoclaw/
config/
@ -8,3 +9,7 @@ config/
*.md
LICENSE
assets/
doc/
docker-compose.yml
Dockerfile.goreleaser
.goreleaser.yaml

View file

@ -3,7 +3,7 @@
# ============================================================
FROM golang:1.26.0-alpine AS builder
RUN apk add --no-cache git make
RUN apk add --no-cache git make ca-certificates tzdata
WORKDIR /src
@ -13,31 +13,35 @@ RUN go mod download
# Copy source and build
COPY . .
RUN make build
RUN CGO_ENABLED=0 make build GOFLAGS="-v -trimpath" LDFLAGS='-ldflags "-s -w"'
# Create non-root user entry for scratch
RUN echo "picoclaw:x:10001:10001::/home/picoclaw:/sbin/nologin" > /tmp/passwd && \
echo "picoclaw:x:10001:" > /tmp/group && \
mkdir -p /home/picoclaw
# ============================================================
# Stage 2: Minimal runtime image
# Stage 2: Minimal runtime image (scratch)
# ============================================================
FROM alpine:3.23
FROM scratch
RUN apk add --no-cache ca-certificates tzdata curl
# Copy user/group files for non-root execution
COPY --from=builder /tmp/passwd /etc/passwd
COPY --from=builder /tmp/group /etc/group
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -q --spider http://localhost:18790/health || exit 1
# Copy SSL certs and timezone data
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# Copy home directory (owned by picoclaw user)
COPY --from=builder --chown=10001:10001 /home/picoclaw /home/picoclaw
# Copy binary
COPY --from=builder /src/build/picoclaw /usr/local/bin/picoclaw
# Create non-root user and group
RUN addgroup -g 1000 picoclaw && \
adduser -D -u 1000 -G picoclaw picoclaw
USER 10001
# Switch to non-root user
USER picoclaw
# Run onboard to create initial directories and config
RUN /usr/local/bin/picoclaw onboard
EXPOSE 18790
ENTRYPOINT ["picoclaw"]
CMD ["gateway"]

View file

@ -1,10 +1,24 @@
FROM alpine:3.21
# syntax=docker/dockerfile:1
# Certs stage — provides CA certificates and timezone data for scratch
FROM alpine:3.23 AS certs
RUN apk add --no-cache ca-certificates tzdata
RUN echo "picoclaw:x:10001:10001::/home/picoclaw:/sbin/nologin" > /tmp/passwd && \
echo "picoclaw:x:10001:" > /tmp/group
# Runtime
FROM scratch
ARG TARGETPLATFORM
RUN apk add --no-cache ca-certificates tzdata
COPY --from=certs /tmp/passwd /etc/passwd
COPY --from=certs /tmp/group /etc/group
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=certs /usr/share/zoneinfo /usr/share/zoneinfo
COPY $TARGETPLATFORM/picoclaw /usr/local/bin/picoclaw
USER 10001
ENTRYPOINT ["picoclaw"]
CMD ["gateway"]

View file

@ -8,6 +8,14 @@ services:
context: .
dockerfile: Dockerfile
container_name: picoclaw-agent
user: "10001"
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
tmpfs:
- /tmp
profiles:
- agent
volumes:
@ -27,12 +35,18 @@ services:
dockerfile: Dockerfile
container_name: picoclaw-gateway
restart: unless-stopped
user: "10001"
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
tmpfs:
- /tmp
profiles:
- gateway
volumes:
# Configuration file
- ./config/config.json:/home/picoclaw/.picoclaw/config.json:ro
# Persistent workspace (sessions, memory, logs)
- picoclaw-workspace:/home/picoclaw/.picoclaw/workspace
command: [ "gateway" ]