Build Configurations
This document provides detailed reference for matrix.json files and build configuration patterns.
Overview
Each application has a matrix.json file that defines:
- Which components to build
- Which architectures to support
- Platform mappings for Docker Buildx
The matrix is consumed by GitHub Actions to create parallel build jobs.
Matrix.json Structure
Basic Schema
[
{
"component": "string",
"arch": "string",
"platforms": "string"
}
]Field Definitions
component (required)
Type: String
Description: Component name used in image tags and workflow logic
Format:
- Lowercase alphanumeric with hyphens
- Must match component name in source repository
- Used in final image name:
ghcr.io/namespace/COMPONENT:tag
Examples:
"component": "mattermost"
"component": "outline"arch (required)
Type: String
Description: Target CPU architecture
Allowed values:
amd64- Intel/AMD 64-bit (x86_64)arm64- ARM 64-bit (aarch64)arm/v7- ARM 32-bit (for specialized cases)
Examples:
"arch": "amd64"
"arch": "arm64"Usage in workflow:
- Artifact naming:
digests-{component}-{arch} - Binary compilation:
GOARCH=${arch} - Platform selection:
linux/${arch}
platforms (required)
Type: String
Description: Docker platform identifier for Buildx
Format: os/architecture[/variant]
Common values:
"platforms": "linux/amd64"
"platforms": "linux/arm64"
"platforms": "linux/arm/v7"Usage in workflow:
docker build --platform ${{ matrix.images.platforms }} ...Matrix Patterns
Pattern 1: Single Component, Two Architectures
Use case: Simple applications (Mattermost, Outline)
File: apps/myapp/matrix.json
[
{
"component": "myapp",
"arch": "amd64",
"platforms": "linux/amd64"
},
{
"component": "myapp",
"arch": "arm64",
"platforms": "linux/arm64"
}
]Result:
- 2 parallel builds
- 1 multi-arch manifest
- Image:
ghcr.io/namespace/myapp:tag
Workflow matrix:
strategy:
matrix:
images: ${{ fromJSON(needs.infos.outputs.matrix) }}
# Creates 2 jobs:
# - build (myapp, amd64)
# - build (myapp, arm64)Pattern 2: Multiple Components, Two Architectures
Use case: Multi-component systems (microservices architectures)
File: apps/myapp/matrix.json
[
{
"component": "myapp-api",
"arch": "amd64",
"platforms": "linux/amd64"
},
{
"component": "myapp-api",
"arch": "arm64",
"platforms": "linux/arm64"
},
{
"component": "myapp-worker",
"arch": "amd64",
"platforms": "linux/amd64"
},
{
"component": "myapp-worker",
"arch": "arm64",
"platforms": "linux/arm64"
},
{
"component": "myapp-ui",
"arch": "amd64",
"platforms": "linux/amd64"
},
{
"component": "myapp-ui",
"arch": "arm64",
"platforms": "linux/arm64"
}
]Result:
- 6 parallel builds (3 components × 2 arches)
- 3 multi-arch manifests (one per component)
- Images:
ghcr.io/namespace/myapp-api:tagghcr.io/namespace/myapp-worker:tagghcr.io/namespace/myapp-ui:tag
Workflow matrix:
# Build job: 6 parallel jobs
strategy:
matrix:
images: ${{ fromJSON(needs.infos.outputs.matrix) }}
# Merge job: 3 parallel jobs (one per component)
strategy:
matrix:
component: ["myapp-api", "myapp-worker", "myapp-ui"]Pattern 3: Single Architecture Only
Use case: Testing or amd64-only builds
File: apps/myapp/matrix.json
[
{
"component": "myapp",
"arch": "amd64",
"platforms": "linux/amd64"
}
]Result:
- 1 build job
- 1 single-arch manifest
- Faster build times for testing
Workflow filtering:
- name: Read matrix
run: |
if [ "${{ inputs.MULTI_ARCH }}" = "true" ]; then
echo "matrix=$(cat apps/myapp/matrix.json | jq -c .)"
else
echo "matrix=$(cat apps/myapp/matrix.json | jq -c '[.[] | select(.arch == "amd64")]')"
fiMatrix Validation
JSON Syntax Validation
# Validate JSON syntax
cat apps/myapp/matrix.json | jq .
# Should output prettified JSON if valid
# Should show error if invalidSchema Validation
Required fields check:
cat apps/myapp/matrix.json | jq '.[] | select(.component == null or .arch == null or .platforms == null)'
# Should return nothing if all entries validArchitecture values check:
cat apps/myapp/matrix.json | jq '.[] | select(.arch != "amd64" and .arch != "arm64")'
# Should return nothing if all arches validPlatform format check:
cat apps/myapp/matrix.json | jq '.[] | select(.platforms | test("^linux/(amd64|arm64|arm/v7)$") | not)'
# Should return nothing if all platforms validTesting Matrix Filtering
Get amd64 only:
cat apps/myapp/matrix.json | jq '[.[] | select(.arch == "amd64")]'Get arm64 only:
cat apps/myapp/matrix.json | jq '[.[] | select(.arch == "arm64")]'Get specific component:
cat apps/myapp/matrix.json | jq '[.[] | select(.component == "myapp-api")]'Count total builds:
cat apps/myapp/matrix.json | jq 'length'List all components:
cat apps/myapp/matrix.json | jq '[.[].component] | unique'List all architectures:
cat apps/myapp/matrix.json | jq '[.[].arch] | unique'Workflow Integration
Reading Matrix in Workflow
- name: Read matrix configuration
id: matrix
run: |
MATRIX=$(cat apps/myapp/matrix.json | jq -c .)
echo "matrix=${MATRIX}" >> $GITHUB_OUTPUTConditional Matrix Loading
- name: Read matrix with conditional architectures
id: matrix
run: |
if [ "${{ inputs.MULTI_ARCH }}" = "true" ]; then
# Load full matrix (all architectures)
echo "matrix=$(cat apps/myapp/matrix.json | jq -c .)" >> $GITHUB_OUTPUT
else
# Load amd64 only
echo "matrix=$(cat apps/myapp/matrix.json | jq -c '[.[] | select(.arch == "amd64")]')" >> $GITHUB_OUTPUT
fiUsing Matrix in Build Job
build:
strategy:
matrix:
images: ${{ fromJSON(needs.infos.outputs.matrix) }}
steps:
- name: Access matrix values
run: |
echo "Component: ${{ matrix.images.component }}"
echo "Architecture: ${{ matrix.images.arch }}"
echo "Platform: ${{ matrix.images.platforms }}"Using Matrix in Merge Job
For single component:
merge:
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
pattern: digests-myapp-*
path: /tmp/digests/myappFor multiple components:
merge:
strategy:
matrix:
component: ["myapp-api", "myapp-worker", "myapp-ui"]
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
pattern: digests-${{ matrix.component }}-*
path: /tmp/digests/${{ matrix.component }}Matrix Best Practices
1. Component Naming
Good:
"component": "myapp-api"
"component": "postgres"Avoid:
"component": "MyApp" // CamelCase
"component": "myapp_api" // Underscores
"component": "my app" // Spaces
"component": "app-v2" // Version in name2. Consistent Ordering
Recommended order:
- Group by component
- Order architectures: amd64, arm64, arm/v7
[
{"component": "api", "arch": "amd64", "platforms": "linux/amd64"},
{"component": "api", "arch": "arm64", "platforms": "linux/arm64"},
{"component": "worker", "arch": "amd64", "platforms": "linux/amd64"},
{"component": "worker", "arch": "arm64", "platforms": "linux/arm64"}
]3. Documentation
Add comments in README about matrix structure:
## Build Matrix
- **Components:** 3 (api, worker, ui)
- **Architectures:** 2 (amd64, arm64)
- **Total builds:** 6 (3 × 2)
Matrix defined in `matrix.json`.4. Testing Strategy
Test incrementally:
- Start with 1 component, 1 architecture (1 build)
- Add 1 more architecture (2 builds)
- Add more components as needed
5. Performance Considerations
Build parallelism:
- GitHub Actions free tier: 20 concurrent jobs
- Each matrix entry = 1 job
- Keep total entries reasonable (<20 for free tier)
Example:
- 8 components × 2 arches = 16 jobs ✅ (under limit)
- 15 components × 2 arches = 30 jobs ⚠️ (may queue)
Advanced Matrix Patterns
Dynamic Component List
Instead of hardcoding component names in merge job:
# In infos job - extract components from matrix
- name: Get component list
id: components
run: |
COMPONENTS=$(cat apps/myapp/matrix.json | jq -c '[.[].component] | unique')
echo "components=${COMPONENTS}" >> $GITHUB_OUTPUT
# In merge job - use dynamic list
merge:
strategy:
matrix:
component: ${{ fromJSON(needs.infos.outputs.components) }}Conditional Builds Based on Component
- name: Compile binary
if: |
matrix.images.component == 'api' ||
matrix.images.component == 'worker'
run: ./compile.shAdding Metadata to Matrix
[
{
"component": "api",
"arch": "amd64",
"platforms": "linux/amd64",
"dockerfile": "Dockerfile.api",
"requires_compilation": true
}
]Access in workflow:
- name: Set Dockerfile path
run: |
echo "DOCKERFILE=${{ matrix.images.dockerfile || 'Dockerfile' }}" >> $GITHUB_ENVMatrix Examples
Example 1: Mattermost
File: apps/mattermost/matrix.json
[
{
"component": "mattermost",
"arch": "amd64",
"platforms": "linux/amd64"
},
{
"component": "mattermost",
"arch": "arm64",
"platforms": "linux/arm64"
}
]Characteristics:
- Single component
- 2 architectures
- Simple pattern
- 2 total builds
Example 2: Outline
File: apps/outline/matrix.json
[
{
"component": "outline",
"arch": "amd64",
"platforms": "linux/amd64"
},
{
"component": "outline",
"arch": "arm64",
"platforms": "linux/arm64"
}
]Characteristics:
- Single component
- 2 architectures
- Node.js application
- No compilation needed
- 2 total builds
Example 3: Generic Microservices
File: apps/myapp/matrix.json
[
{
"component": "myapp-api",
"arch": "amd64",
"platforms": "linux/amd64"
},
{
"component": "myapp-api",
"arch": "arm64",
"platforms": "linux/arm64"
},
{
"component": "myapp-worker",
"arch": "amd64",
"platforms": "linux/amd64"
},
{
"component": "myapp-worker",
"arch": "arm64",
"platforms": "linux/arm64"
},
{
"component": "myapp-scheduler",
"arch": "amd64",
"platforms": "linux/amd64"
},
{
"component": "myapp-scheduler",
"arch": "arm64",
"platforms": "linux/arm64"
},
{
"component": "myapp-ui",
"arch": "amd64",
"platforms": "linux/amd64"
},
{
"component": "myapp-ui",
"arch": "arm64",
"platforms": "linux/arm64"
}
]Characteristics:
- 4 components (API, Worker, Scheduler, UI)
- 2 architectures per component
- 8 total builds
- 4 multi-arch manifests
Troubleshooting Matrix Issues
Issue: Matrix Empty in Workflow
Symptom: Build job doesn't run, matrix is empty
Check:
# Verify JSON is valid
cat apps/myapp/matrix.json | jq .
# Verify file path in workflow is correct
# Should be: cat apps/myapp/matrix.jsonIssue: Wrong Number of Build Jobs
Symptom: Expecting 4 jobs, only 2 run
Check:
# Count matrix entries
cat apps/myapp/matrix.json | jq 'length'
# Check for duplicates
cat apps/myapp/matrix.json | jq 'group_by(.component, .arch) | map(select(length > 1))'Issue: Build Job Uses Wrong Values
Symptom: amd64 job tries to build arm64
Check workflow:
# Ensure correct platform is used
platforms: ${{ matrix.images.platforms }} # ✅ Correct
# Not:
platforms: linux/${{ matrix.images.arch }} # ❌ May not match platforms fieldFurther Reading
- GitHub Actions Matrix Strategy
- jq Manual - JSON processing