Skip to content

Add single-container Docker appliance and multi-arch publishing #17

Add single-container Docker appliance and multi-arch publishing

Add single-container Docker appliance and multi-arch publishing #17

Workflow file for this run

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