test&build workflow (memodb-io/memobase)
The test&build workflow from memodb-io/memobase, explained and optimized by Latchkey.
CI health: C - fair
Point runs-on at Latchkey and get run de-duplication, job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the test&build workflow from the memodb-io/memobase repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: test&build
on:
merge_group:
workflow_dispatch:
push:
branches:
- main
- dev
tags:
- 'v*'
paths:
- '.github/workflows/publish.yaml'
- 'src/server/api/**'
- '!src/server/api/**/*.md'
pull_request:
branches:
- main
- dev
paths:
- '.github/workflows/publish.yaml'
- 'src/server/api/**'
- '!src/server/api/**/*.md'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
name: Tests on ${{ matrix.os }} for ${{ matrix.python-version }}
strategy:
matrix:
python-version: [3.12]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Set up Python
run: uv python install
- name: Install dependencies
working-directory: ./src/server/api
run: |
uv sync --frozen --no-dev --no-cache-dir
- name: Start containers
working-directory: ./src/server
run: |
cp .env.example .env
cp api/config.yaml.example api/config.yaml
sh script/up-dev.sh &
- name: Wait for containers to start
working-directory: ./src/server
run: |
until docker compose exec memobase-server-db pg_isready -U $DATABASE_USER -d $DATABASE_NAME; do
echo "Waiting for PostgreSQL to be ready..."
sleep 5
done
- name: Test with pytest
working-directory: ./src/server/api
run: |
cp .env.example .env
uv run -m pytest --junit-xml=junit/test-results-${{ matrix.python-version }}.xml --cov=. --cov-report=xml:coverage-${{ matrix.python-version }}.xml tests/ -s -v
- name: Upload pytest test results and coverage
uses: actions/upload-artifact@v4
with:
name: pytest-results-${{ matrix.python-version }}
path: |
./src/server/api/junit/test-results-${{ matrix.python-version }}.xml
./src/server/api/coverage-${{ matrix.python-version }}.xml
if: ${{ always() }}
- name: Stop containers
working-directory: ./src/server
if: ${{ always() }}
run: |
docker compose down
build-image:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
SHOULD_PUSH: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork) || github.event_name == 'workflow_dispatch' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Login to the Container Registry
if: ${{ env.SHOULD_PUSH == 'true' }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and Push Docker image
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
context: ./src/server/api
push: ${{ env.SHOULD_PUSH == 'true' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: test&build on: merge_group: workflow_dispatch: push: branches: - main - dev tags: - 'v*' paths: - '.github/workflows/publish.yaml' - 'src/server/api/**' - '!src/server/api/**/*.md' pull_request: branches: - main - dev paths: - '.github/workflows/publish.yaml' - 'src/server/api/**' - '!src/server/api/**/*.md' env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: timeout-minutes: 30 name: Tests on ${{ matrix.os }} for ${{ matrix.python-version }} strategy: matrix: python-version: [3.12] os: [ubuntu-latest] runs-on: ${{ matrix.os }} permissions: contents: read steps: - uses: actions/checkout@v4 - name: Install uv uses: astral-sh/setup-uv@v6 - name: Set up Python run: uv python install - name: Install dependencies working-directory: ./src/server/api run: | uv sync --frozen --no-dev --no-cache-dir - name: Start containers working-directory: ./src/server run: | cp .env.example .env cp api/config.yaml.example api/config.yaml sh script/up-dev.sh & - name: Wait for containers to start working-directory: ./src/server run: | until docker compose exec memobase-server-db pg_isready -U $DATABASE_USER -d $DATABASE_NAME; do echo "Waiting for PostgreSQL to be ready..." sleep 5 done - name: Test with pytest working-directory: ./src/server/api run: | cp .env.example .env uv run -m pytest --junit-xml=junit/test-results-${{ matrix.python-version }}.xml --cov=. --cov-report=xml:coverage-${{ matrix.python-version }}.xml tests/ -s -v - name: Upload pytest test results and coverage uses: actions/upload-artifact@v4 with: name: pytest-results-${{ matrix.python-version }} path: | ./src/server/api/junit/test-results-${{ matrix.python-version }}.xml ./src/server/api/coverage-${{ matrix.python-version }}.xml if: ${{ always() }} - name: Stop containers working-directory: ./src/server if: ${{ always() }} run: | docker compose down build-image: timeout-minutes: 30 needs: test runs-on: latchkey-small permissions: contents: read packages: write env: SHOULD_PUSH: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork) || github.event_name == 'workflow_dispatch' }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Login to the Container Registry if: ${{ env.SHOULD_PUSH == 'true' }} uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and Push Docker image uses: docker/build-push-action@v6 with: platforms: linux/amd64,linux/arm64 context: ./src/server/api push: ${{ env.SHOULD_PUSH == 'true' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cancel superseded runs when a branch or PR gets a newer push.
- Add a job timeout so a hung step cannot burn hours of runner time.
6 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Container pulls and builds
This workflow runs 2 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.