diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 039d68d..be56403 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -4,7 +4,10 @@ # timeplus/tpk all-in-one image (timeplusd + tpk) -> all-in-one mode # # Tags produced: `latest` (main only), the git short SHA, and — on a vX.Y.Z tag — -# the semver `X.Y.Z` and `X.Y`. +# the semver `X.Y.Z` and `X.Y`. Every pushed tag is a multi-arch manifest list +# (linux/amd64 + linux/arm64, issue #72) so the images run natively on Apple +# Silicon and on ARM (Graviton) Kubernetes nodes. The arm64 leg is built under +# QEMU; pull requests build linux/amd64 only as a smoke test. # # Required repository secrets: # DOCKERHUB_USERNAME / DOCKERHUB_TOKEN Docker Hub creds with push access to @@ -12,7 +15,7 @@ # Optional (only if the timeplusd base image needs auth to pull): # TIMEPLUSD_REGISTRY_USERNAME / TIMEPLUSD_REGISTRY_PASSWORD for docker.timeplus.com # -# Pull requests build the app image as a smoke test but never push. +# Pull requests build the app image (amd64 only) as a smoke test but never push. name: Docker @@ -30,6 +33,10 @@ concurrency: permissions: contents: read +env: + # Multi-arch for anything we publish; PRs stay single-arch to keep CI fast. + PLATFORMS: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }} + jobs: app: name: app image (timeplus/tpk-app) @@ -37,6 +44,10 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + - uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub @@ -63,7 +74,7 @@ jobs: context: . file: deploy/docker/Dockerfile target: app - platforms: linux/amd64 + platforms: ${{ env.PLATFORMS }} push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} @@ -82,6 +93,10 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + - uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub @@ -115,7 +130,7 @@ jobs: context: . file: deploy/docker/Dockerfile target: allinone - platforms: linux/amd64 + platforms: ${{ env.PLATFORMS }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/Makefile b/Makefile index f71b3f1..36e3f3a 100644 --- a/Makefile +++ b/Makefile @@ -97,11 +97,20 @@ down-allinone: ## Stop the all-in-one stack (named data volumes are kept) APP_IMAGE ?= timeplus/tpk-app:dev -docker-build-allinone: ## Build the all-in-one image (timeplusd + tpk) - docker build -f deploy/docker/Dockerfile --target allinone -t $(IMAGE) . - -docker-build-app: ## Build the tpk app-only image (pure-Python; no timeplusd) - docker build -f deploy/docker/Dockerfile --target app -t $(APP_IMAGE) . +# Local builds target the host arch. Set PLATFORMS for a cross/multi-arch build +# via buildx, e.g. +# make docker-build-app PLATFORMS=linux/arm64 +# make docker-build PLATFORMS=linux/amd64,linux/arm64 DOCKER_BUILD_FLAGS=--push +# (a multi-platform build can't be loaded into the local daemon; add --push). +PLATFORMS ?= +DOCKER_BUILD_FLAGS ?= +DOCKER_BUILD = docker $(if $(PLATFORMS),buildx build --platform $(PLATFORMS),build) $(DOCKER_BUILD_FLAGS) + +docker-build-allinone: ## Build the all-in-one image (proton + tpk); PLATFORMS=... for multi-arch + $(DOCKER_BUILD) -f deploy/docker/Dockerfile --target allinone -t $(IMAGE) . + +docker-build-app: ## Build the tpk app-only image (pure-Python; no timeplusd); PLATFORMS=... for multi-arch + $(DOCKER_BUILD) -f deploy/docker/Dockerfile --target app -t $(APP_IMAGE) . docker-build: docker-build-allinone docker-build-app ## Build both deployable images diff --git a/README.md b/README.md index 09bf18c..67fbde5 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,9 @@ single container (fully open-source, no separate database, no build): docker exec tpk tpk ingest # build the knowledge graph open http://localhost:8000 # chat + web UI — log in as admin / changeme -Use `-e ANTHROPIC_API_KEY=...` instead for Claude; the same key powers both chat -and semantic ingest. `tpk ingest` fetches the repos from the image's baked +The image is multi-arch (`linux/amd64` + `linux/arm64`), so it runs natively on +Apple Silicon. Use `-e ANTHROPIC_API_KEY=...` instead for Claude; the same key +powers both chat and semantic ingest. `tpk ingest` fetches the repos from the image's baked config (`deploy/docker/repos.container.toml`) — private repos need `-e GITHUB_TOKEN=...`. You'll be prompted to change the seed `admin` password on first login. diff --git a/deploy/k8s/README.md b/deploy/k8s/README.md index 0dc58c4..7de0af6 100644 --- a/deploy/k8s/README.md +++ b/deploy/k8s/README.md @@ -17,6 +17,8 @@ so they're alternatives, not layers. The images are the ones published by [`.github/workflows/docker-publish.yml`](../../.github/workflows/docker-publish.yml): `timeplus/tpk-app` (app-only) and `timeplus/tpk` (all-in-one). Pin `:latest` to a released tag (e.g. `timeplus/tpk-app:1.2.3`) for reproducible rollouts. +Both are multi-arch (`linux/amd64` + `linux/arm64`), so they schedule onto +x86 and ARM (e.g. Graviton) nodes alike without a `nodeSelector`. ## Prerequisites