This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This repository contains demos for Flight Control, an edge device management system. Each demo showcases different edge computing use cases by providing pre-configured OS images that include the Flight Control agent and application-specific components.
The repository is organized into two main categories:
- base/centos-bootc/ - CentOS Stream 9 bootc base image
- base/fedora-bootc/ - Fedora 43 bootc base image
Both base images include:
- Flight Control agent (from
rpm.flightctl.io) - podman and podman-compose
- cloud-init and open-vm-tools for provisioning
The demos/ directory contains more specialized demos that build upon base images:
quadlet-wordpress-demo- WordPress using Podman Quadletmlops-pins-fleet- ML operations demoinverter-fleet- IoT inverter management demo
Each image (base or demo) follows this structure:
image-name/
├── bootc/
│ ├── Containerfile.amd64 # x86_64 container build definition
│ ├── Containerfile.arm64 # ARM64 container build definition
│ ├── Containerfile.tags # Custom tags to apply (e.g., timestamped versions)
│ └── etc/ # (demos only) Additional files to include
├── deploy/
│ └── fleet.yaml # Flight Control fleet definition
└── configuration/ # (demos only) Runtime configuration
└── etc/All image builds are performed via GitHub Actions workflows. There are no local build scripts.
The repository uses the following GitHub Actions workflows for building and publishing images:
1. Build & Publish bootc image (build-bootc-image.yaml)
Trigger: Called manually or from other workflows
Purpose: Reusable workflow for building and (optionally) publishing a bootc image.
What it does:
- Builds a bootc image for the specified demo for both amd64 and arm64 architectures (
demos/DEMO_NAME/bootc/**) - Optionally, adds a specified agent config to the image.
- If
dry_runis not specified or isfalse, creates a multi-architecture manifest list. - If
dry_runis not specified or isfalse, signs and publishes the manifest list and images to the specified registry.
Build time: ~5-10 minutes
2. Build & Publish disk images (build-bootc-diskimage.yaml)
Trigger: Called manually or from other workflows
Purpose: Reusable workflow for building and publishing .raw, .iso, and .qcow2 disk images for the specified bootc image.
What it does:
- Pulls the specified bootc image for both amd64 and arm64 architectures.
- Uses bootc image builder to build
.raw,.qcow2, and.isoartifacts for both amd64 and arm64 architectures. - Signs and publishes these artifacts to the specified registry.
Build time: ~5-10 minutes
3. Build & Test (build-and-test.yaml)
Trigger: Pull requests (opened, synchronize, reopened)
Purpose: Fast feedback for PRs without publishing to registry
What it does:
- Detects which bootc images changed (by looking for changes in
**/bootc/**) - Builds changed bootc images for both amd64 and arm64 architectures
- Runs
bootc container lint --fatal-warningson built images - Does NOT build disk images (too slow for PR validation)
- Does NOT push images to registry
Build time: ~5-10 minutes
4. Publish on Merge (publish-on-merge.yaml)
Trigger: Push to main branch (after PR merge)
Purpose: Automatically build and publish validated changes
What it does:
- Detects which bootc images changed in the merge
- Builds changed bootc images for both amd64 and arm64 architectures
- Runs
bootc container lint --fatal-warningson built images - Builds disk images (ISO, RAW, QCoW2) for all architectures
- Creates multi-architecture manifest lists
- Signs all images with Sigstore
- Publishes to quay.io/flightctl-demos with tags:
latestand<commit-sha>
Build time: ~30+ minutes (includes disk images)
5. Update Dependencies (update-dependencies.yaml)
Trigger: Nightly at 2 AM UTC, or manually via workflow_dispatch
Purpose: Automatically detect and update upstream dependencies
What it does:
- Checks base images: Uses
skopeoto query OCI registries for new digests of upstream base images- CentOS:
quay.io/centos-bootc/centos-bootc:stream9 - Fedora:
quay.io/fedora/fedora-bootc:43
- CentOS:
- Checks RPM packages: Parses repository metadata from
rpm.flightctl.iofor flightctl-agent updates- EPEL 9 (for CentOS):
https://rpm.flightctl.io/epel/9/x86_64 - Fedora 43:
https://rpm.flightctl.io/fedora/43/x86_64
- EPEL 9 (for CentOS):
- Updates files when changes detected:
- Updates
Containerfile.{amd64,arm64}with new image digests - Updates
ARG FLIGHTCTL_VERSIONwith new RPM package versions - Creates timestamped tags in
Containerfile.tags(format:stream9-202601171430) - Updates demo Containerfiles that reference base images to use the new timestamped tags
- Updates
- Creates a single PR with all updates bundled together for easy review
Build time: ~2-3 minutes
Each image directory contains a Containerfile.tags file that specifies custom tags to apply when building that image. This enables:
- Timestamped versions: Base images get dated tags (e.g.,
stream9-202601171430) when upstream updates are detected - Reproducible builds: Demo images can reference specific timestamped base image versions
- Audit trail: Each update creates a unique tag, making it easy to track which version was deployed when
Format: Comma-separated list of tags, one per line or all on one line:
stream9-202601171430
latest
How it works:
- Build workflows automatically read
Containerfile.tagswhen building images - Tags from the file are merged with workflow-provided tags (like
latestand${GITHUB_SHA}) - All tags are applied to both the bootc image and its disk image artifacts
Nightly update process:
- Workflow detects new upstream base image digest using
skopeo inspect - Updates
Containerfile.{amd64,arm64}with new@sha256:...digest - Generates timestamped tag:
<base-tag>-<YYYYMMDDHHmm>(e.g.,stream9-202601171430) - Writes timestamped tag to
Containerfile.tags - Updates demo images that depend on this base to use the new timestamped tag
- Creates PR with all changes
Example workflow:
- Upstream
centos-bootc:stream9gets a new digest - Nightly workflow updates
base/centos-bootc/bootc/Containerfile.tagstostream9-202601171430 - On next build, centos-bootc image gets tagged with both
stream9-202601171430andlatest - Any demos with bootc images that reference the base would be updated to use the timestamped version
Built images are pushed to the OCI registry with the following structure:
- Bootc image:
quay.io/flightctl-demos/DEMO_NAME:TAG - Disk images:
- ISO:
quay.io/flightctl-demos/DEMO_NAME/diskimage-iso:TAG(OCI artifact) - RAW:
quay.io/flightctl-demos/DEMO_NAME/diskimage-raw:TAG(OCI artifact) - QCoW2:
quay.io/flightctl-demos/DEMO_NAME/diskimage-qcow2:TAG(OCI image for KubeVirt)
- ISO:
Available tags:
latest- Always points to the most recent build from main branch<commit-sha>- Git commit SHA (first 8 chars) for the build<base-tag>-<timestamp>- Timestamped versions for base images (e.g.,stream9-202601171430)- Only present when upstream base image was updated
- Timestamp format: YYYYMMDDHHmm (year, month, day, hour, minute in UTC)
- Allows pinning to specific base image versions for reproducibility
Example tags for centos-bootc:
quay.io/flightctl-demos/centos-bootc:latest
quay.io/flightctl-demos/centos-bootc:abc12345
quay.io/flightctl-demos/centos-bootc:stream9-202601171430
ISO and RAW formats are stored as OCI artifacts and must be downloaded using ORAS:
oras pull quay.io/flightctl-demos/centos-bootc/diskimage-iso:latest
# Or pull a specific timestamped version
oras pull quay.io/flightctl-demos/centos-bootc/diskimage-iso:stream9-202601171430- bootc = "bootable container" - containers that can boot as operating systems
- Images are built as standard OCI containers but contain a full OS filesystem
- bootc-image-builder converts bootc images to bootable disk images
- Each Containerfile installs packages, configures systemd services, and runs
bootc container lint
- All images support both amd64 and arm64 architectures
- Separate Containerfiles per architecture allow arch-specific customization
- GitHub Actions matrix builds both architectures in parallel
- Final manifest lists reference both architectures
- Agent enrollment: Images can include pre-baked agent config (early binding) or enroll at runtime (late binding)
- Fleet: A collection of devices with shared configuration defined in
deploy/fleet.yaml - Fleet spec includes:
os.image: The bootc image to deployconfig: Inline files, git-sourced configuration, or secretssystemd.matchPatterns: Services to monitor
- ISO: Bootable installation media for physical provisioning
- RAW: Raw disk image for direct writes (dd, Raspberry Pi Imager, etc.)
- QCoW2: QEMU copy-on-write format, packaged as "containerdisk" for KubeVirt
When modifying Containerfile.amd64 or Containerfile.arm64:
-
Package installation: Always install minimal and clean up package metadata to reduce image size:
RUN dnf -y install PACKAGE \ --nodocs --setopt=install_weak_deps=False && \ dnf clean all && \ rm -rf /var/{cache,log} -
Enable systemd services: Use
systemctl enableto ensure services start on boot:RUN systemctl enable SERVICE_NAME.service -
Agent configuration: The build workflow can inject agent config by appending:
ADD config.yaml /etc/flightctl/ -
Linting: Including
RUN bootc container lintas the final step is recommended for early feedback during local builds. However, CI workflows enforce linting automatically, so forgotten lints won't slip through -
Keep architecture variants in sync: Changes to one architecture should typically be mirrored to the other unless there's an architecture-specific reason
Fleet YAML files in deploy/ directories define how devices are managed:
apiVersion: flightctl.io/v1beta1
kind: Fleet
metadata:
name: fleet-name
spec:
selector:
matchLabels:
fleet: fleet-name
template:
spec:
os:
image: quay.io/flightctl-demos/IMAGE_NAME:latest
config:
- name: config-name
inline:
- path: "/etc/file"
content: "content"
mode: 0644
- name: git-config
gitRef:
repository: repo-name
targetRevision: main
path: /path/to/config
systemd:
matchPatterns:
- "service-name.service"Apply fleets using the flightctl CLI:
flightctl apply -f deploy/fleet.yamlTo add a new demo image:
-
Create directory structure:
mkdir -p NEW_DEMO/{bootc,deploy,configuration/etc} -
Create
bootc/Containerfile.amd64andbootc/Containerfile.arm64:- Demos must use local base images (they include the flightctl-agent):
- CentOS-based:
FROM quay.io/flightctl-demos/centos-bootc:latest - Fedora-based:
FROM quay.io/flightctl-demos/fedora-bootc:latest
- CentOS-based:
- Install required packages
- Add configuration files with
ADDstatements - Enable systemd services
- Run
bootc container lint
- Demos must use local base images (they include the flightctl-agent):
-
Create
bootc/Containerfile.tagswith initial tags:echo "latest" > NEW_DEMO/bootc/Containerfile.tags
-
Create
deploy/fleet.yamlwith appropriate fleet configuration -
(Optional) Add runtime configuration in
configuration/ -
Open a PR - the PR validation workflow will automatically build and test your new image. After merge, it will be published to the registry
If you need to fork this repo and build images with your own Flight Control enrollment:
- GitHub account
- OCI registry account (Quay.io recommended) with robot credentials
skopeo1.16+ installed locallyflightctlCLI configured and authenticated
Follow the detailed steps in the main README.md "Building your own bootc and disk images" section to:
- Generate agent enrollment config
- Create Sigstore signing keys
- Configure GitHub secrets
- Trigger workflow builds