docker #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: docker | |
| # Build and publish multi-arch images to ghcr.io on every git tag (v*). | |
| # | |
| # Tags produced for ``v12.3.0``: | |
| # ghcr.io/vbcherepanov/total-agent-memory:12.3.0 | |
| # ghcr.io/vbcherepanov/total-agent-memory:12.3 | |
| # ghcr.io/vbcherepanov/total-agent-memory:12 | |
| # ghcr.io/vbcherepanov/total-agent-memory:latest | |
| # | |
| # Also runnable manually via workflow_dispatch with an explicit tag — used | |
| # to backfill missing ghcr tags (e.g. 12.1, 12.2) without cutting a new | |
| # git tag. | |
| # | |
| # Smoke step: pull the freshly-pushed amd64 image, run it, hit /healthz, | |
| # fail the workflow if 200 doesn't come back within 60s. | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Version to publish (e.g. 12.3.0). Defaults to git ref tag.' | |
| required: false | |
| default: '' | |
| permissions: | |
| contents: read | |
| packages: write | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository_owner }}/total-agent-memory | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Resolve version | |
| id: version | |
| run: | | |
| if [ -n "${{ inputs.tag }}" ]; then | |
| v="${{ inputs.tag }}" | |
| else | |
| v="${GITHUB_REF_NAME#v}" | |
| fi | |
| if ! echo "$v" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "::error::expected semver MAJOR.MINOR.PATCH, got '$v'" | |
| exit 1 | |
| fi | |
| echo "version=$v" >> "$GITHUB_OUTPUT" | |
| echo "major=$(echo "$v" | cut -d. -f1)" >> "$GITHUB_OUTPUT" | |
| echo "minor=$(echo "$v" | cut -d. -f1-2)" >> "$GITHUB_OUTPUT" | |
| - name: Set up QEMU (for arm64 cross-build) | |
| uses: docker/setup-qemu-action@v3.6.0 | |
| - name: Set up Buildx | |
| uses: docker/setup-buildx-action@v3.10.0 | |
| - name: Login to ghcr | |
| uses: docker/login-action@v3.4.0 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build + push (amd64 + arm64) | |
| uses: docker/build-push-action@v6.18.0 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| platforms: linux/amd64,linux/arm64 | |
| push: true | |
| provenance: false | |
| tags: | | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }} | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.minor }} | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.major }} | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest | |
| labels: | | |
| org.opencontainers.image.title=total-agent-memory | |
| org.opencontainers.image.description=Persistent memory MCP server for AI coding agents | |
| org.opencontainers.image.source=https://github.com/${{ github.repository }} | |
| org.opencontainers.image.version=${{ steps.version.outputs.version }} | |
| org.opencontainers.image.licenses=MIT | |
| org.opencontainers.image.url=https://totalmemory.dev | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| smoke: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Pull just-pushed image (amd64) | |
| run: | | |
| docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.version }} | |
| - name: Run + probe /healthz + /api/stats | |
| run: | | |
| set -e | |
| docker volume create tam-ci-data | |
| docker run -d --name tam-ci \ | |
| -p 37737:37737 \ | |
| -v tam-ci-data:/data \ | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.version }} | |
| # Wait up to 60s for /healthz to come up. | |
| for i in $(seq 1 30); do | |
| if curl -fsS http://127.0.0.1:37737/healthz >/dev/null 2>&1; then | |
| echo "✓ /healthz responding after ${i}*2s" | |
| break | |
| fi | |
| if [ "$i" = "30" ]; then | |
| echo "::error::/healthz did not respond within 60s" | |
| docker logs tam-ci | |
| exit 1 | |
| fi | |
| sleep 2 | |
| done | |
| # Wait up to 60s for /api/stats (depends on DB migration). | |
| for i in $(seq 1 30); do | |
| code=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:37737/api/stats) | |
| if [ "$code" = "200" ]; then | |
| echo "✓ /api/stats → 200 after ${i}*2s" | |
| break | |
| fi | |
| if [ "$i" = "30" ]; then | |
| echo "::error::/api/stats did not return 200 within 60s (last: $code)" | |
| docker logs tam-ci | |
| exit 1 | |
| fi | |
| sleep 2 | |
| done | |
| # Check Docker's own healthcheck reports healthy. | |
| for i in $(seq 1 20); do | |
| h=$(docker inspect tam-ci --format '{{.State.Health.Status}}') | |
| if [ "$h" = "healthy" ]; then | |
| echo "✓ docker healthcheck = healthy" | |
| break | |
| fi | |
| if [ "$i" = "20" ]; then | |
| echo "::error::docker healthcheck never reached 'healthy' (last: $h)" | |
| docker logs tam-ci | |
| exit 1 | |
| fi | |
| sleep 3 | |
| done | |
| - name: Cleanup | |
| if: always() | |
| run: | | |
| docker rm -f tam-ci 2>/dev/null || true | |
| docker volume rm tam-ci-data 2>/dev/null || true |