Skip to content

Commit c355c9f

Browse files
fix(container): repair Containerfile build rot + runtime.exs env config (#273)
## What Makes `container/Containerfile` build and produce a **runnable Elixir REST image**, and adds `config/runtime.exs` so runtime env is honoured. Discovered while deploying boj-server to Hetzner (now **live** — `https://api.boj-server.net/health` → 200). ## Six build-rot fixes 1. **.dockerignore** excluded `scripts/fetch-cartridges.sh` (needed by a COPY) and `schemas/cartridge-v1.json` (Catalog schema mirror) → un-ignored both. (Refs #271) 2. **Zig pin** — replaced unpinned `apk add zig` (wolfi rolls forward; newer Zig removed `std.heap.GeneralPurposeAllocator`) with the repo-declared **0.15.1** tarball (`.tool-versions`). 3. **Elixir builder** — `cgr.dev/chainguard/elixir:latest` is now **403/auth-gated** → public `docker.io/elixir:1.18.4-otp-25`. 4. **Runtime base** — `cgr.dev/chainguard/wolfi-base` lost `gcompat`/`ncurses-libs` and its OpenSSL 3 breaks the OTP-25 crypto NIF (needs `libcrypto.so.1.1`) → **`debian:bullseye-slim`** (matches builder ABI). 5. **Schema** — COPY `schemas/cartridge-v1.json` into `/schemas` (Catalog requires it at boot, else RuntimeError). 6. **.zig-cache strip** — the cartridge tree carried multi-GB Zig caches copied into the runtime stage, exhausting build disk; stripped in the builder stage. ## runtime.exs `config/runtime.exs` re-reads `BOJ_PORT` / `BOJ_BIND_IP` / `BOJ_CARTRIDGES_ROOT` / `BOJ_DATA_DIR` at **release boot**. Without it, `mix release` bakes config.exs's env lookups at *build* time, so: - the listener bound `127.0.0.1` (unreachable via port-map — required an ugly `--network host` workaround), and - `cartridges_root` was a build path → **`cartridges_loaded:0`**. Both are fixed by evaluating env at runtime. ## Validation All six fixes applied and **verified end-to-end on the Hetzner host** — image builds, container runs, `/health` returns `{"status":"ok"}`. Once a `v*` tag build runs with this Containerfile, `container-publish.yml` will publish a correct `:latest` (the current ghcr `:latest`/`:main` are a stale Node MCP-bridge image — #272; this PR is the fix). Closes #271. Refs #272. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5362680 commit c355c9f

3 files changed

Lines changed: 35 additions & 5 deletions

File tree

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,5 @@ data/
8383
configs/
8484
k8s/
8585
setup.sh
86+
!scripts/fetch-cartridges.sh
87+
!schemas/cartridge-v1.json

container/Containerfile

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ ARG BOJ_CARTRIDGES_REPO=https://github.com/hyperpolymath/boj-server-cartridges.g
2424

2525
FROM cgr.dev/chainguard/wolfi-base:latest AS zig-builder
2626

27-
RUN apk add --no-cache zig build-base git bash
27+
RUN apk add --no-cache build-base git bash curl xz
28+
# Pin Zig to the repo-declared version (.tool-versions: 0.15.1); wolfi apk zig rolls forward and breaks the build
29+
RUN curl -fsSL https://ziglang.org/download/0.15.1/zig-x86_64-linux-0.15.1.tar.xz -o /tmp/zig.tar.xz \
30+
&& mkdir -p /opt/zig && tar -xJf /tmp/zig.tar.xz -C /opt/zig --strip-components=1
31+
ENV PATH=/opt/zig:${PATH}
2832

2933
WORKDIR /build
3034

@@ -59,7 +63,10 @@ RUN for ffi_dir in /build/cartridges/*/ffi; do \
5963
# Stage 2: Elixir Release Builder
6064
# ============================================================================
6165

62-
FROM cgr.dev/chainguard/elixir:latest AS mix-builder
66+
# Strip Zig build caches (multi-GB, runtime-irrelevant) so the cartridge COPY into the runtime stage stays small
67+
RUN find /build/cartridges -type d -name .zig-cache -prune -exec rm -rf {} + 2>/dev/null || true
68+
69+
FROM docker.io/elixir:1.18.4-otp-25 AS mix-builder
6370

6471
WORKDIR /build
6572

@@ -77,7 +84,7 @@ RUN mix release boj_rest
7784
# Stage 3: Runtime Image
7885
# ============================================================================
7986

80-
FROM cgr.dev/chainguard/wolfi-base:latest
87+
FROM docker.io/debian:bullseye-slim
8188

8289
# OCI labels
8390
LABEL org.opencontainers.image.title="Bundle of Joy Server" \
@@ -90,10 +97,10 @@ LABEL org.opencontainers.image.title="Bundle of Joy Server" \
9097
dev.stapeln.compose="container/compose.toml"
9198

9299
# Minimal runtime deps: OpenSSL (Elixir TLS), ncurses (Erlang), glibc (boj-invoke + .so)
93-
RUN apk add --no-cache ca-certificates openssl ncurses-libs libstdc++ gcompat
100+
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates openssl libncurses6 libstdc++6 && rm -rf /var/lib/apt/lists/*
94101

95102
# Non-root user
96-
RUN addgroup -S appuser && adduser -S appuser -G appuser
103+
RUN groupadd -r appuser && useradd -r -g appuser -d /app appuser
97104

98105
WORKDIR /app
99106

@@ -144,6 +151,10 @@ ENV PORT=7700
144151
ENV MIX_ENV=prod
145152

146153
VOLUME ["/data"]
154+
# Cartridge schema mirror (Catalog validates cartridges against /schemas/cartridge-v1.json)
155+
COPY schemas/cartridge-v1.json /app/schemas/cartridge-v1.json
156+
COPY schemas/cartridge-v1.json /schemas/cartridge-v1.json
157+
147158
USER appuser
148159

149160
EXPOSE 7700

elixir/config/runtime.exs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
import Config
4+
5+
# Runtime configuration — evaluated at release BOOT, not compile time. Without this,
6+
# a `mix release` bakes config.exs's env lookups at build time, so BOJ_BIND_IP /
7+
# BOJ_PORT / BOJ_CARTRIDGES_ROOT set at `podman run` are ignored (the release keeps
8+
# the build-time values: bind 127.0.0.1 unreachable via port-map, and a build-path
9+
# cartridges_root that yields cartridges_loaded:0). Re-reading them here fixes both.
10+
config :boj_rest,
11+
port: String.to_integer(System.get_env("BOJ_PORT") || "7700"),
12+
bind_ip: System.get_env("BOJ_BIND_IP") || "127.0.0.1",
13+
cartridges_root:
14+
System.get_env("BOJ_CARTRIDGES_ROOT") ||
15+
System.get_env("BOJ_CARTRIDGES_PATH") ||
16+
Path.expand("../../cartridges", __DIR__),
17+
data_dir: System.get_env("BOJ_DATA_DIR") || "/data"

0 commit comments

Comments
 (0)