Add single-container Docker appliance and multi-arch publishing #12
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 appliance | |
| on: | |
| pull_request: | |
| push: | |
| branches: | |
| - master | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| packages: write | |
| concurrency: | |
| group: docker-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| IMAGE_NAME: ghcr.io/magiccodingman/truthgate-ipfs | |
| jobs: | |
| validate-compose: | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Validate production Compose | |
| run: docker compose -f compose.yaml config | |
| - name: Validate development override | |
| run: docker compose -f compose.yaml -f compose.dev.yaml config | |
| smoke-test: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - architecture: amd64 | |
| runner: ubuntu-24.04 | |
| - architecture: arm64 | |
| runner: ubuntu-24.04-arm | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build production image (${{ matrix.architecture }}) | |
| shell: bash | |
| run: | | |
| set -o pipefail | |
| docker build \ | |
| --progress=plain \ | |
| --platform linux/${{ matrix.architecture }} \ | |
| --target production \ | |
| --tag truthgate-ipfs:test-${{ matrix.architecture }} \ | |
| . 2>&1 | tee production-image-build-${{ matrix.architecture }}.log | |
| - name: Upload production build diagnostics | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: production-image-build-${{ matrix.architecture }} | |
| path: production-image-build-${{ matrix.architecture }}.log | |
| if-no-files-found: error | |
| - name: Build development image (${{ matrix.architecture }}) | |
| shell: bash | |
| run: | | |
| set -o pipefail | |
| docker build \ | |
| --progress=plain \ | |
| --platform linux/${{ matrix.architecture }} \ | |
| --target development \ | |
| --tag truthgate-ipfs:dev-test-${{ matrix.architecture }} \ | |
| . 2>&1 | tee development-image-build-${{ matrix.architecture }}.log | |
| - name: Upload development build diagnostics | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: development-image-build-${{ matrix.architecture }} | |
| path: development-image-build-${{ matrix.architecture }}.log | |
| if-no-files-found: ignore | |
| - name: Start, replace, and revalidate the appliance | |
| shell: bash | |
| run: | | |
| exec > >(tee runtime-smoke-${{ matrix.architecture }}.log) 2>&1 | |
| set -Eeuo pipefail | |
| export TRUTHGATE_IMAGE=truthgate-ipfs:test-${{ matrix.architecture }} | |
| export TRUTHGATE_HTTP_PORT=18080 | |
| export TRUTHGATE_HTTPS_PORT=18443 | |
| export IPFS_SWARM_PORT=14001 | |
| export TRUTHGATE_DATA_HOST_PATH=./.ci-data/truthgate | |
| export IPFS_REPO_HOST_PATH=./.ci-data/ipfs/repo | |
| export IPFS_BLOCKS_HOST_PATH=./.ci-data/ipfs/blocks | |
| cleanup() { | |
| docker compose logs --no-color 2>/dev/null || true | |
| docker compose down --remove-orphans 2>/dev/null || true | |
| } | |
| trap cleanup EXIT | |
| wait_for_health() { | |
| for attempt in $(seq 1 60); do | |
| status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' truthgate)" | |
| if [[ "${status}" == "healthy" ]]; then | |
| return 0 | |
| fi | |
| if [[ "${status}" == "unhealthy" ]]; then | |
| echo "TruthGate became unhealthy." | |
| return 1 | |
| fi | |
| sleep 5 | |
| done | |
| echo "TruthGate did not become healthy in time." | |
| return 1 | |
| } | |
| read_peer_id() { | |
| docker exec truthgate \ | |
| ipfs --api=/ip4/127.0.0.1/tcp/5001 id \ | |
| | python3 -c 'import json, sys; print(json.load(sys.stdin)["ID"])' | |
| } | |
| read_bootstrap_hash() { | |
| docker exec truthgate \ | |
| sha256sum /data/truthgate/state/bootstrap-admin-password \ | |
| | cut -d' ' -f1 | |
| } | |
| mkdir -p .ci-data/truthgate .ci-data/ipfs/repo .ci-data/ipfs/blocks | |
| docker compose up --detach --no-build | |
| wait_for_health | |
| # Read protected state from inside the container. The host runner is | |
| # intentionally unable to inspect the bootstrap secret directly. | |
| docker exec truthgate test -s /data/ipfs/repo/config | |
| docker exec truthgate test -s /data/truthgate/state/bootstrap-admin-password | |
| peer_id_before="$(read_peer_id)" | |
| bootstrap_hash_before="$(read_bootstrap_hash)" | |
| # Replace the container while retaining only the documented bind-mounted | |
| # state. The Kubo identity and first-run credential must remain stable. | |
| docker compose down --remove-orphans | |
| docker compose up --detach --no-build | |
| wait_for_health | |
| docker exec truthgate test -s /data/ipfs/repo/config | |
| docker exec truthgate test -s /data/truthgate/state/bootstrap-admin-password | |
| peer_id_after="$(read_peer_id)" | |
| bootstrap_hash_after="$(read_bootstrap_hash)" | |
| test "${peer_id_before}" = "${peer_id_after}" | |
| test "${bootstrap_hash_before}" = "${bootstrap_hash_after}" | |
| docker compose logs --no-color | |
| - name: Upload runtime diagnostics | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: runtime-smoke-${{ matrix.architecture }} | |
| path: runtime-smoke-${{ matrix.architecture }}.log | |
| if-no-files-found: ignore | |
| build-and-publish: | |
| if: github.event_name != 'pull_request' | |
| needs: | |
| - validate-compose | |
| - smoke-test | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: docker/setup-qemu-action@v3 | |
| - uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Generate image metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.IMAGE_NAME }} | |
| tags: | | |
| type=raw,value=master,enable=${{ github.ref == 'refs/heads/master' }} | |
| type=sha,prefix=sha- | |
| type=ref,event=tag | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| - name: Publish AMD64 + ARM64 under one image tag | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| target: production | |
| platforms: linux/amd64,linux/arm64 | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| provenance: mode=max | |
| sbom: true |