# syntax=docker/dockerfile:1.6 # # Dockerfile.mc-ai — Linux container for mc-ai autoplay batches on apricot. # # Purpose: provide a single docker-cgroup boundary around the entire autoplay # batch (build artifact + Godot processes) so a runaway sim cannot wedge the # host the way bare `systemd-run --user` did. Mirrors the freeze-proofing # pattern from Dockerfile.godot/godot-docker.sh, applied to the simulator # half of the loop. # # Build context = the per-run scratch worktree (the same path apricot-run.sh # launcher.sh creates via `git worktree add`). The image bakes the # libmagic_civ_physics_gdext.so artifact for that exact SHA so cargo never # runs at container start. Tag the image with the short SHA: # # docker build --tag mc-ai:$(git rev-parse --short HEAD) \ # --file tools/docker/Dockerfile.mc-ai $SCRATCH # # At runtime, scripts/mc-ai-docker.sh bind-mounts the worktree at /work and # the entrypoint installs the baked .so into the worktree's addons dir before # exec'ing the requested command. # # Base-image digest refresh: # docker buildx imagetools inspect debian:bookworm-slim # → index digest # docker buildx imagetools inspect rust:1-bookworm # → index digest # # ── stage 1: build the GDExtension shared object ──────────────────────────── FROM rust@sha256:6258907abe69656e41cd992e0b705cdcfabcbbe3db374f92ed2d47121282d4a1 AS builder # build-essential + pkg-config + libssl-dev cover the native deps wgpu / # tokio / openssl-sys transitively pull in. Keep this list short — the # simulator workspace is intentionally light on C deps. RUN apt-get update \ && apt-get install -y --no-install-recommends \ build-essential \ pkg-config \ libssl-dev \ && rm -rf /var/lib/apt/lists/* # Copy only what build-gdext.sh actually needs to keep cargo's input set # minimal — full game/ tree (.tscn/.gd/.import etc.) is not part of the # Rust build. WORKDIR /build COPY src/simulator ./src/simulator # Several mc-* crates `include_str!("../../../../../public/...")` at compile # time (mc-comms/config.rs, mc-trade/rules.rs, mc-score/lib.rs, mc-ecology # generation.rs, etc.) — the JSON game pack + resources tree must be present # during the cargo build, not just at runtime. COPY public ./public # BuildKit cache mounts persist cargo registry + target dir across image # rebuilds, so the second build of any SHA reuses everything that didn't # change. CARGO_TARGET_DIR is set so build-gdext.sh's find_build helper # locates the .so under /build-target/. RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \ --mount=type=cache,target=/build-target,sharing=locked \ set -eux; \ cd src/simulator; \ CARGO_TARGET_DIR=/build-target \ bash build-gdext.sh x86_64-unknown-linux-gnu; \ mkdir -p /out; \ cp /build-target/x86_64-unknown-linux-gnu/release/libmagic_civ_physics_gdext.so \ /out/libmagic_civ_physics.x86_64.so # ── stage 2: runtime — Godot headless + the baked .so ─────────────────────── FROM debian@sha256:0104b334637a5f19aa9c983a91b54c89887c0984081f2068983107a6f6c21eeb AS runtime ARG GODOT_VERSION=4.6.2 ARG GODOT_RELEASE=stable ENV DEBIAN_FRONTEND=noninteractive # Mirrors Dockerfile.godot's runtime deps. coreutils + util-linux cover the # nice/ionice calls autoplay-batch.sh wraps each seed in (its systemd-run # branch is auto-skipped inside the container since systemd-run is absent). RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ wget \ unzip \ coreutils \ util-linux \ libgl1-mesa-glx \ libfontconfig1 \ libxi6 \ libxrandr2 \ libxcursor1 \ libxinerama1 \ libasound2 \ libpulse0 \ && rm -rf /var/lib/apt/lists/* RUN set -eux; \ arch="$(dpkg --print-architecture)"; \ case "$arch" in \ arm64) godot_arch="linux.arm64" ;; \ amd64) godot_arch="linux.x86_64" ;; \ *) echo "unsupported arch: $arch" >&2; exit 1 ;; \ esac; \ url="https://github.com/godotengine/godot-builds/releases/download/${GODOT_VERSION}-${GODOT_RELEASE}/Godot_v${GODOT_VERSION}-${GODOT_RELEASE}_${godot_arch}.zip"; \ wget -q -O /tmp/godot.zip "$url"; \ unzip -q /tmp/godot.zip -d /opt; \ mv /opt/Godot_v${GODOT_VERSION}-${GODOT_RELEASE}_${godot_arch} /usr/local/bin/godot; \ chmod +x /usr/local/bin/godot; \ rm /tmp/godot.zip # uid/gid 1000 matches the apricot operator account so bind-mount writes # (RESULTS dir, addons/ install) land with host-friendly ownership. RUN useradd --create-home --uid 1000 --shell /bin/bash mcai # Baked simulator artifact — entrypoint installs it into the bind-mounted # worktree at container start. COPY --from=builder /out/libmagic_civ_physics.x86_64.so /opt/mc-ai/libmagic_civ_physics.x86_64.so RUN chown -R mcai:mcai /opt/mc-ai # Entrypoint copies the baked .so into the worktree's addons dir so Godot # loads the image's exact SHA-matched artifact (overriding anything the # bind-mounted worktree might carry). Exec form preserves signal handling. COPY tools/docker/mc-ai-entrypoint.sh /usr/local/bin/mc-ai-entrypoint RUN chmod 0755 /usr/local/bin/mc-ai-entrypoint USER mcai WORKDIR /work ENTRYPOINT ["/usr/local/bin/mc-ai-entrypoint"] CMD ["godot", "--version"]