1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# Armature Microservice Dockerfile
# Generic template for individual services
ARG SERVICE_NAME=service
# The chef/planner boilerplate below (Stages 1-2) is intentionally duplicated
# across all 4 Armature Dockerfiles rather than extracted into a shared base
# image, since this repo builds each with plain `docker build` (no buildx
# bake infra). Keep RUST_VERSION / CARGO_CHEF_VERSION in sync across:
# docker/full-stack/Dockerfile
# docker/microservices/Dockerfile.service
# docker/microservices/Dockerfile.worker
# docker/observability/Dockerfile.api
ARG RUST_VERSION=1.94.1
# Pinned to a specific cargo-chef release (not "latest") for reproducible
# builds. Update periodically: https://github.com/LukeMathWalker/cargo-chef/releases
ARG CARGO_CHEF_VERSION=0.1.68
# ==================================================
# Stage 1: Chef - base image with cargo-chef installed
# ==================================================
FROM lukemathwalker/cargo-chef:${CARGO_CHEF_VERSION}-rust-${RUST_VERSION}-alpine AS chef
WORKDIR /app
# ==================================================
# Stage 2: Planner - compute the dependency-only recipe
# ==================================================
FROM chef AS planner
# The whole workspace (all Cargo.toml manifests + sources) is required for
# `cargo chef prepare` to resolve the dependency graph, but this stage only
# ever produces recipe.json, which is all that gets copied forward.
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
# ==================================================
# Stage 3: Build
# ==================================================
FROM chef AS builder
ARG SERVICE_NAME
WORKDIR /app
RUN apk add --no-cache musl-dev openssl-dev openssl-libs-static pkgconfig
# Build dependencies only. This layer is cache-keyed on recipe.json (which is
# itself derived only from Cargo.toml/Cargo.lock), so it is skipped entirely
# on rebuilds that only change application source.
COPY --from=planner /app/recipe.json recipe.json
# Scoped to the single example this image actually builds (rest_api)
# instead of --examples, so the cached dependency layer isn't inflated by
# every other example in the workspace.
RUN cargo chef cook --release --example rest_api --recipe-path recipe.json
COPY . .
# Build service example (replace with actual service in production)
RUN cargo build --release --example rest_api && \
strip /app/target/release/examples/rest_api
# Runtime stage
FROM alpine:3.19
WORKDIR /app
RUN apk add --no-cache ca-certificates curl && \
adduser -D -u 1000 armature
COPY --from=builder /app/target/release/examples/rest_api /app/service
USER armature
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
ENTRYPOINT ["/app/service"]