gitmote

gitmote  ·  history  ·  branches

feat: add make image/prod/publish to dogfood the container locally

c9afb94e33ede8cd8dc502b165032c5d5ca5bd57
Atanas Minev <atmin@protonmail.com> · 2026-07-05 17:42

diff --git a/Makefile b/Makefile
index 8d8cfea..cde7bfe 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,23 @@ GOIMPORTS := go run golang.org/x/tools/cmd/goimports@v0.47.0
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
-.PHONY: all fmt lint test build dev dev-reset e2e-local e2e-restore
+# Public GHCR server image (CI publishes it on master; the targets below are the
+# local build/run/publish path — see docs/ops.md). The CI *runner* image
+# (ghcr.io/atmin/gitmote-runner) is amd64-only and self-tests act at build, so it
+# can't build under local emulation on an arm64 host — it's published by CI
+# (.github/workflows/publish-runner.yml) or by the manual amd64 path in ops.md.
+IMAGE_SERVER := ghcr.io/atmin/gitmote
+
+# Host port for `make prod`. Defaults to the prod-parity 8080; override to run the
+# container alongside a native `make dev` (which owns 8080) — the shared bucket's
+# writer lease then makes whichever starts second a read-only follower.
+PORT ?= 8080
+
+# Dev compose project (docker-compose.dev.yml sets name: gitmote-dev). `make prod`
+# joins this project's network so the container reaches MinIO at minio:9000.
+DEV_COMPOSE := docker compose -p gitmote-dev -f docker-compose.dev.yml
+
+.PHONY: all fmt lint test build dev dev-reset e2e-local e2e-restore image prod publish
all: lint test build
@@ -47,3 +63,33 @@ e2e-local:
# production cold-start path locally. Requires docker + docker compose.
e2e-restore:
./scripts/e2e-restore.sh
+
+# Build the actual server container image (distinct from `make build`, the Go
+# binaries), tagged with VERSION — the exact image CI publishes and `make prod`
+# runs. (The runner image is CI/amd64-only; see the IMAGE_SERVER note above.)
+image:
+ docker build -t $(IMAGE_SERVER):$(VERSION) --build-arg VERSION=$(VERSION) .
+
+# Run the built server image against the dev MinIO, sharing the `gitmote` bucket so
+# it restores the same forge state as `make dev` — the local dogfood of the exact
+# prod deployment (portable container, restore-from-S3). No cookie key / worker
+# secret env: they auto-generate and persist in meta, exactly as in prod. Serves
+# the UI on :$(PORT). Requires docker + docker compose; run `make dev-reset` to wipe.
+prod: image
+ $(DEV_COMPOSE) up -d minio
+ $(DEV_COMPOSE) run --rm mc
+ @net=$$(docker network ls --format '{{.Name}}' | grep -m1 gitmote-dev || echo gitmote-dev_default); \
+ echo "--- running $(IMAGE_SERVER):$(VERSION) against dev MinIO (network $$net) on :$(PORT) ---"; \
+ docker run --rm -p $(PORT):8080 --network "$$net" \
+ -e GITMOTE_S3_BUCKET=gitmote \
+ -e GITMOTE_S3_ENDPOINT=http://minio:9000 \
+ -e AWS_REGION=us-east-1 \
+ -e AWS_ACCESS_KEY_ID=minioadmin \
+ -e AWS_SECRET_ACCESS_KEY=minioadmin \
+ $(IMAGE_SERVER):$(VERSION)
+
+# Push the built server image to GHCR — the manual / break-glass path (CI
+# publishes on master). Requires `docker login ghcr.io` first (a PAT with
+# write:packages). The runner image publishes via CI / the ops.md amd64 path.
+publish: image
+ docker push $(IMAGE_SERVER):$(VERSION)
diff --git a/tasks/README.md b/tasks/README.md
index 51e340c..367cae3 100644
--- a/tasks/README.md
+++ b/tasks/README.md
@@ -12,12 +12,8 @@ envs, on a laptop or on Scaleway, identically. Kill it — fine; restart it —
restores from S3 and continues. Localhost is a first-class deployment.
Chain A (shrink the surface — auto-generated secrets, first-run auto-bootstrap)
-has landed. What remains is the image chain plus a docs cap — order within the
-chain matters:
-
-**Chain B — the image**
-- [dogfood-make.md](dogfood-make.md) — `make image` / `make prod` / `make publish`;
- `make prod` runs the real image against dev MinIO.
+and Chain B (the image — public GHCR, `make image`/`prod`/`publish`) have landed.
+All that remains is the docs cap:
**Cap**
- [operate-docs.md](operate-docs.md) — `docker run` quickstart (incl. "grab the
diff --git a/tasks/dogfood-make.md b/tasks/dogfood-make.md
deleted file mode 100644
index 8cb0d91..0000000
--- a/tasks/dogfood-make.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# Dogfood Make targets — build, run, publish the image
-
-Part of [easy to operate](README.md). Operator ergonomics for the container
-lifecycle. The bucket-derived replica already lets `make prod` restore the
-*whole* forge from S3; this depends on [public-registry](public-registry.md)
-(the image tags).
-
-## Spec
-
-Three targets, distinct from `make build` (which stays the Go binaries):
-
-- `make image` — `docker build` the server (and runner) image, tagged
- `ghcr.io/atmin/gitmote:$(VERSION)` / `…/gitmote-runner:$(VERSION)`.
-- `make prod` — run **that image** against the dev MinIO, sharing the `gitmote`
- bucket so it sees the same state as `make dev`, on `:8080`. This is the local
- dogfood of the exact prod deployment — proof the container is portable and
- restores from S3.
-- `make publish` — `docker push` the image(s) to GHCR (after `docker login
- ghcr.io`). CI publishes on master; this is the manual / first-time path.
-
-## Current
-
-The Makefile builds Go binaries (`make build`) and runs gitmote **natively** via
-`make dev`. There is no way to exercise the actual container image locally, so the
-"portable container, localhost is valid" claim is untested outside prod.
-
-## Change
-
-- Add `image` / `prod` / `publish` targets. `make prod` reaches MinIO over the dev
- compose network, not the host port: `--network gitmote-dev_default` (resolve the
- name robustly — `podman compose` may name it differently),
- `GITMOTE_S3_ENDPOINT=http://minio:9000`, `GITMOTE_S3_BUCKET=gitmote`, MinIO creds.
-- Reuse the dev MinIO (bring it up if needed).
-
-## Verify
-
-- `make image && make prod` boots the built image against the dev MinIO, shares
- `make dev`'s state (meta + objects, post-replica-task), and serves the UI on
- `:8080` — the portability / restore-from-S3 proof.
-- Run **one at a time** with `make dev`: the lease makes the second instance a
- follower that 503s reads (correct single-writer behavior) — verify that's what
- happens, not a crash.
-- `make publish` pushes to GHCR (manual login).