# syntax=docker/dockerfile:1 # ---- Stage 0: Cache Context Fallback ---- FROM scratch AS gomodcache # ---- Stage 1: Build base with dependencies ---- FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS build-base WORKDIR /build RUN apk add --no-cache git make COPY go.mod go.sum ./ RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=bind,from=gomodcache,target=/tmp/gomodcache \ set -eux; \ if [ -d /tmp/gomodcache/cache/download ]; then \ cp -a /tmp/gomodcache/. /go/pkg/mod/; \ fi; \ go mod download COPY . . # ---- Stage 2: Build server binary ---- FROM build-base AS server-builder ARG VERSION=dev ARG COMMIT_HASH=unknown ARG BUILD_TIME=unknown ARG TARGETOS ARG TARGETARCH RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ set -eux; \ CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \ go build -trimpath \ -ldflags "-s -w \ -X github.com/memohai/memoh/internal/version.Version=${VERSION} \ -X github.com/memohai/memoh/internal/version.CommitHash=${COMMIT_HASH} \ -X github.com/memohai/memoh/internal/version.BuildTime=${BUILD_TIME}" \ -o memoh-server ./cmd/agent/main.go # ---- Stage 3: Build bridge binary ---- FROM build-base AS bridge-builder ARG TARGETARCH ARG COMMIT_HASH=unknown RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH:-amd64} \ go build -trimpath \ -ldflags "-s -w -X github.com/memohai/memoh/internal/version.CommitHash=${COMMIT_HASH}" \ -o /out/bridge ./cmd/bridge # ---- Stage 4: Assemble workspace runtime + toolkit ---- FROM alpine:latest AS toolkit-assembly ARG TARGETARCH RUN apk add --no-cache xz COPY docker/toolkit/install.sh /tmp/install.sh RUN /tmp/install.sh /assembly/toolkit "${TARGETARCH:-amd64}" # Assemble runtime directory COPY --from=bridge-builder /out/bridge /assembly/bridge COPY cmd/bridge/template /assembly/templates COPY docker/toolkit/bin /assembly/toolkit/bin RUN chmod +x /assembly/toolkit/bin/* # ---- Stage 5: Final runtime (containerd + server + CNI) ---- FROM alpine:latest WORKDIR /app # containerd runtime RUN apk add --no-cache containerd containerd-ctr # CNI plugins + iptables (for workspace container networking) RUN apk add --no-cache ca-certificates tzdata wget cni-plugins iptables \ && mkdir -p /opt/cni/bin \ && (cp -a /usr/lib/cni/. /opt/cni/bin/ 2>/dev/null || true) \ && (cp -a /usr/libexec/cni/. /opt/cni/bin/ 2>/dev/null || true) \ && mkdir -p /etc/cni/net.d /var/lib/cni \ && printf '%s\n' \ '{' \ ' "cniVersion": "1.0.0",' \ ' "name": "memoh-cni",' \ ' "plugins": [' \ ' {' \ ' "type": "bridge",' \ ' "bridge": "cni0",' \ ' "isGateway": true,' \ ' "ipMasq": true,' \ ' "hairpinMode": true,' \ ' "ipam": {' \ ' "type": "host-local",' \ ' "ranges": [[' \ ' { "subnet": "10.88.0.0/16" }' \ ' ]],' \ ' "routes": [' \ ' { "dst": "0.0.0.0/0" }' \ ' ]' \ ' }' \ ' },' \ ' {' \ ' "type": "portmap",' \ ' "capabilities": { "portMappings": true }' \ ' }' \ ' ]' \ '}' > /etc/cni/net.d/10-memoh.conflist # Workspace runtime (bind-mounted into bot containers) COPY --from=toolkit-assembly /assembly /opt/memoh/runtime # Server binary, spec, and provider presets COPY --from=server-builder /build/memoh-server /app/memoh-server COPY --from=server-builder /build/spec /app/spec COPY --from=server-builder /build/conf/providers /app/conf/providers # Entrypoint: start containerd, then server COPY docker/server-entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh RUN mkdir -p /opt/memoh/data /run/containerd /var/lib/containerd VOLUME ["/var/lib/containerd", "/opt/memoh/data"] EXPOSE 8080 1455 HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:8080/health \ || wget --no-verbose --tries=1 --spider http://server:8080/health \ || exit 1 ENTRYPOINT ["/entrypoint.sh"]