Skip to content

Preparing a repository ​

The builder fetches your repository at one commit and builds what it finds in the build context — the repository root unless you name a subdirectory. Three layouts are supported:

LayoutYou addTypical build time
Plain Dockerfilea Dockerfileminutes
pvpkg package with a Dockerfilepvpkg.json, a Dockerfile, optional pvpkg/ overlayminutes
Yocto recipepvpkg.json with build.kind: yocto, kas files, your layerminutes from the shared cache; hours cold
Buildroot recipepvpkg.json with build.kind: buildroot, a defconfigtens of minutes

Repositories must be reachable over HTTPS on GitHub or GitLab. Private ones need a git secret.

Plain Dockerfile ​

Any repository with a Dockerfile builds as is:

text
my-app/
├── Dockerfile
└── src/…

The Dockerfile is built for each requested architecture (use FROM --platform=$BUILDPLATFORM and TARGETARCH for cross builds, or base images that exist for every architecture) and packaged as a Pantavisor container named after the repository, or the app you give the build. Paths can be changed with the build's Dockerfile and context settings; both must stay inside the repository.

pvpkg.json ​

pvpkg.json is the packaging manifest used by Pantacor's container CI (container-ci): a repository that already builds there builds the same way here. It sits at the top of the build context, beside an optional pvpkg/ folder:

text
my-app/
├── pvpkg.json
├── Dockerfile
└── pvpkg/
    ├── generic/            # laid over every architecture's export
    │   └── _config/my-app/etc/my-app/config.json
    └── arm64v8/            # only over the arm64 export
        └── my-app/src.json
json
{
  "name": "my-app",
  "url": "ghcr.io/acme/my-app:main",
  "package_url": "https://github.com/acme/my-app",
  "arch": ["arm64v8", "amd64"],
  "dockerfile": "Dockerfile",
  "src_extra": {
    "args": { "PV_GROUP": "app" }
  }
}
KeyMeaning
nameThe app's name in the export and on the device.
urlThe image name (and tag base) recorded in src.json.
archArchitectures the package exists for: amd64, arm64v8, arm32v6, arm32v7. A build of another architecture is refused. Omit to allow all.
versionThe package version; see Versions.
dockerfileThe Dockerfile, relative to the manifest.
typesinglearch tags the image per architecture (arm64v8-1.0.0).
src_extraExtra src.json fields, such as the container's Pantavisor args, or format_options for squashfs (-comp xz, -b 1M, … from an allowed list). Fields the builder generates (docker_*, rootfs_url, pvr, …) cannot be set.
remove_volumesVolumes the image declares (VOLUME) to drop from the container's config.
buildA Yocto or Buildroot recipe instead of the Dockerfile.

The pvpkg/ folder is copied over the pvr checkout of each export: generic/ for every architecture, then the architecture's own folder (amd64/, arm64v8/, arm32v6/). Use it for configuration files (_config/<app>/…), a committed src.json the builder completes, or other parts of the state. It may hold only directories and regular files: a symlink in pvpkg/ fails the build. A .pvr folder in it is skipped.

Yocto ​

A Yocto package builds a root filesystem image with kas and meta-pantavisor, and packages it as the container's rootfs. The manifest names the kas file per architecture and the image recipe:

json
{
  "name": "hello-http",
  "url": "ghcr.io/acme/hello-http:main",
  "package_url": "https://github.com/acme/hello-http",
  "arch": ["arm64v8", "amd64"],
  "build": {
    "kind": "yocto",
    "kas": { "arm64v8": "kas-arm64.yml", "amd64": "kas-amd64.yml" },
    "target": "hello-http-image",
    "config": "config.json",
    "args": "args.json"
  }
}
build keyMeaning
kindyocto
kasThe kas file: one path for every architecture, or an object by architecture.
targetThe image recipe kas builds (bitbake <target>).
configThe container's image config (Entrypoint, Cmd, Env, WorkingDir, User, Volumes, ExposedPorts): a JSON file of the repository, or the object inline.
argsA JSON file with the app's Pantavisor args (PV_GROUP, PV_ROLES, …).
imageThe build environment image. Leave it out for the pinned Pantacor kas image.

A layout that works:

text
hello-http/
├── pvpkg.json
├── kas-arm64.yml          # kas config for arm64v8
├── kas-amd64.yml          # kas config for amd64
├── config.json            # {"Entrypoint": ["/usr/bin/hello-http"], …}
├── args.json              # {"PV_GROUP": "app"}
├── meta-hello/            # your layer: conf/layer.conf, recipes-*/
├── app/                   # the program's source, used by the layer's recipe
└── pvpkg/generic/…        # optional overlay

The kas file lists your layer as the repository itself (a repo entry with no url, like hello: with layers: meta-hello:), plus poky, meta-openembedded, meta-virtualization and meta-pantavisor. The builder adds IMAGE_FSTYPES += "tar.gz" for the target and takes the image's rootfs tarball.

Start from meta-pantavisor CI to build in minutes ​

Runners serve the shared state cache (sstate) and downloads of meta-pantavisor's CI read-only. A build whose kas configuration matches a CI build — same machine, distro, layer commits and local.conf — reuses nearly everything, and only your layer's recipes compile: a few minutes instead of the hour or more a cold build needs for its toolchain.

To get there, copy the CI configuration your container targets (for example kas/build-configs/release/containers/docker-armv8-scarthgap.yaml in meta-pantavisor) into your kas file, keep its machine, distro, layer commits and local_conf_header as they are, and only add your layer and change target. The build's log prints the Sstate summary; aim for a match above 95%. Common layers (poky, meta-openembedded, meta-virtualization, meta-pantavisor, meta-arm) are also mirrored next to the runners, so kas does not clone them from upstream in every build.

Private layers and sources ​

A recipe that fetches from a private repository (a private layer in the kas file, or a SRC_URI like git://github.com/acme/sdk;protocol=https) needs the account's git secret named in the build — see secrets in recipes.

Buildroot ​

A Buildroot package builds a defconfig of your repository and takes output/images/rootfs.tar:

json
{
  "name": "tiny-app",
  "arch": ["arm64v8"],
  "build": {
    "kind": "buildroot",
    "ref": "2025.02.6",
    "defconfig": "configs/tiny_arm64_defconfig",
    "external": "br2-external",
    "config": { "Entrypoint": ["/usr/bin/tiny-app"] }
  }
}
build keyMeaning
kindbuildroot
refThe Buildroot tag or branch (required), such as 2025.02.6.
buildrootThe Buildroot repository (HTTPS). Default: https://gitlab.com/buildroot.org/buildroot.git.
defconfigThe defconfig file: one path, or an object by architecture.
externalA BR2_EXTERNAL tree in the repository.
config, args, imageAs for Yocto.

The defconfig must enable a tar root filesystem (BR2_TARGET_ROOTFS_TAR=y); the builder enables it if missing. A change to the defconfig starts from a clean output directory.

Check before you push ​

  • Every path in pvpkg.json is relative and inside the repository (no .., no absolute paths).
  • arch contains every architecture you will request.
  • The kas files and defconfig exist at the commit you build — the builder never sees uncommitted files.
  • For a private repository, a git secret covers its host and path.

Then push it and build it on stage.