Compose File

holos.yaml is intentionally close to docker-compose, but the unit of work is a VM. Each service becomes one or more QEMU instances with a qcow2 overlay, cloud-init seed, generated SSH access, and optional named volumes.

For editor completion and validation, use the schema at docs/holos.schema.json.

name: hello

services:
  web:
    image: ubuntu:noble
    ports:
      - "8080:80"
    cloud_init:
      packages:
        - nginx
      write_files:
        - path: /var/www/html/index.html
          content: "hello from holos\n"
      runcmd:
        - systemctl restart nginx

Core fields:

Graceful Shutdown

holos stop and holos down send QMP system_powerdown to the guest, then wait up to stop_grace_period for QEMU to exit on its own. If the guest does not halt in time, or QMP is unreachable, the runtime falls back to SIGTERM then SIGKILL.

services:
  db:
    image: ubuntu:noble
    stop_grace_period: 60s

Data Volumes

Top-level volumes: declares named data stores under state_dir/volumes/<project>/<name>.qcow2. They survive holos down; teardown only removes per-instance symlinks. Docker Compose volume metadata fields such as name, driver, driver_opts, external, and labels are accepted for compatibility; Holos uses size for the qcow2 backing file.

name: demo

services:
  db:
    image: ubuntu:noble
    volumes:
      - pgdata:/var/lib/postgresql
      - snapshots:/mnt/snapshots:ro

volumes:
  pgdata:
    size: 20G
  snapshots:
    size: 50G

Top-level networks and per-service networks are accepted for Docker Compose compatibility. Holos currently uses its own internal VM network plan, so these declarations do not alter runtime networking.

Top-level configs/secrets and per-service references are also accepted for Docker Compose compatibility. Holos does not currently distribute these resources into guests; use cloud_init.write_files for files that must be created inside the VM. Config template_driver is accepted as metadata.

Top-level models and per-service models references are accepted for Docker Compose compatibility, including model, context_size, and runtime_flags. They are metadata only in Holos today.

Named volumes attach as virtio-blk devices with stable serials like vol-pgdata, so the guest sees /dev/disk/by-id/virtio-vol-pgdata. For read-write volumes, cloud-init creates an ext4 filesystem and fstab entry. For read-only volumes, holos skips formatting and writes ro,nofail to fstab. If a guest mount fails, the cloud-init command exits non-zero and writes a holos: failed to mount volume ... error to the instance console log.

Healthchecks And depends_on

A service with a healthcheck blocks dependents from starting until the probe passes. The probe runs over SSH using the same generated key as holos exec.

services:
  db:
    image: postgres-cloud.qcow2
    healthcheck:
      test: ["pg_isready", "-U", "postgres"]
      interval: 2s
      retries: 30
      start_period: 10s
      timeout: 3s

  api:
    image: api.qcow2
    depends_on: [db]

test accepts a list (exec form) or a string (wrapped as sh -c). Docker Compose’s test: ["NONE"] and disable: true forms disable the healthcheck. start_interval is accepted for Compose compatibility; Holos currently probes using interval. Failures during start_period do not consume retry budget. Set HOLOS_HEALTH_BYPASS=1 to skip the actual probe in CI environments that cannot SSH into guests.

Networking

Every service can reach every other service by name:

GPU And PCI Passthrough

Pass physical GPUs or other PCI devices directly to a VM via VFIO:

services:
  ml:
    image: ubuntu:noble
    vm:
      vcpu: 8
      memory_mb: 16384
    devices:
      - pci: "01:00.0"
      - pci: "01:00.1"
    ports:
      - "8888:8888"

holos enables UEFI automatically when devices are present, copies OVMF vars per instance, sets NVIDIA-friendly machine options, and accepts optional rom_file paths for custom VBIOS ROMs. You still need host IOMMU setup and the relevant devices bound to vfio-pci.

Images

Use built-in image aliases or local paths:

services:
  web:
    image: alpine
  api:
    image: ubuntu:noble
  db:
    image: ./images/db.qcow2
    image_format: qcow2

Available aliases include alpine, arch, debian, ubuntu, fedora, almalinux, rocky, and centos-stream. Run holos images to see tags and defaults.

For local or private qcow2 images, holos treats the file as an operator-supplied artifact. Set image_format and image_os explicitly, verify the image checksum before holos up, and consider keeping an images/SHA256SUMS or holos.images.lock file next to holos.yaml so teams can review exactly which private image build a project was tested against. See the threat model for verification and lockfile guidance.

Dockerfile Provisioning

A Dockerfile can provision a VM when you want familiar build steps without building a container image. holos translates supported instructions into cloud-init:

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"

build accepts Docker Compose string syntax (build: ./app) and mapping syntax with context and dockerfile. Holos accepts common Compose build metadata such as args, cache_from, extra_hosts, isolation, labels, no_cache, pull, provenance, sbom, shm_size, ssh, tags, target, ulimits, and platforms, but only context, dockerfile, and dockerfile_inline affect provisioning today. additional_contexts accepts both map syntax and NAME=VALUE list syntax.

Supported instructions are FROM, RUN, COPY, ENV, and WORKDIR. Unsupported instructions fail loudly. For example, use services.<name>.ports instead of EXPOSE, services.<name>.healthcheck instead of HEALTHCHECK, and guest systemd units or cloud_init.runcmd instead of CMD / ENTRYPOINT.

When image is omitted, the base image is taken from the Dockerfile’s FROM line. Dockerfile instructions run before cloud_init.runcmd.

COPY sources are resolved relative to the build context for build, or the Dockerfile directory for dockerfile. They must stay inside the context and must be files. Use volumes for directories.

Extra QEMU Arguments

Pass arbitrary flags through with vm.extra_args:

services:
  gpu:
    image: ubuntu:noble
    vm:
      extra_args:
        - "-device"
        - "virtio-gpu-pci"
        - "-display"
        - "egl-headless"

Arguments are appended after holos-managed flags. holos does not validate them.

Defaults