Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Project layout

Sandbox directory

Each project stores its sandbox state locally in .airlock/ next to the config file. A .gitignore containing * is written there automatically so nothing under .airlock/ is tracked by version control.

<project>/
  airlock.toml                   # user config (tracked by VCS)
  .airlock/
    .gitignore                   # contains "*" — auto-created
    airlock.log                  # tracing log from the last run
    sandbox/
      ca.json                    # CA cert + key PEMs (single JSON file)
      overlay/
        files/rw/{key}           # hard-linked writable file mounts
        files/ro/{key}           # hard-linked read-only file mounts
      disk.img                   # virtio-blk ext4 volume
      run.json                   # last-run timestamp, guest_cwd
      run.log                    # tracing log from the last run
      lock                       # PID lockfile (one VM per project)
      cli.sock                   # Unix socket for `airlock exec` RPC
      image                      # hard link to images/<digest> JSON

airlock rm removes the entire .airlock/ directory. The config file is untouched.

The CA is a single file — there is no longer a sandbox/ca/ directory. The PEM bytes read from ca.json are passed to the guest via the start RPC and the guest injects them after mounting overlayfs (see Mounts / CA certificate injection).

CA keypair

On first airlock start, a self-signed CA keypair is generated and written to sandbox/ca.json as a JSON object with cert and key PEM fields. The PEM strings are read into memory once and kept on the Project struct — no further file reads are needed at TLS setup or guest CA injection.

Global cache

~/.cache/airlock/
  vm/
    Image                        # Linux kernel (extracted on first run)
    initramfs.gz                 # initramfs
    cloud-hypervisor             # (Linux only) hypervisor binary
    virtiofsd                    # (Linux only) VirtioFS daemon
    checksum                     # triggers re-extraction after binary update
  oci/
    images/<digest>              # schema-tagged JSON: the fully-baked OciImage
    layers/<digest>/             # extracted layer tree (whiteouts as xattrs)
    layers/<digest>.download.tmp # in-flight download (swept on next run)
    layers/<digest>.download     # complete tarball pending extraction
    layers/<digest>.tmp/         # in-flight extraction (swept on next run)

The image cache is shared across all projects. Layers are content-addressable by digest, so two images that share a base layer extract it only once. Platform is fixed to linux/arm64 (matching the VM architecture).

Each images/<digest> entry is a single JSON file carrying the serialized OciImage (wrapped in a {"schema":"v1", …} envelope for forward-compatible schema evolution). It is written atomically via .tmp rename and then hard-linked into the sandbox at sandbox/image; a link count greater than 1 on the cached file means at least one sandbox references the image, which prevents GC.

A <digest>/ layer directory only exists through the atomic rename from <digest>.tmp/, so its presence is itself the completion marker — no separate .ok file is needed.

sandbox/image serves two purposes: it’s the per-project GC ref, and it’s the stored-image source — reading it as JSON gives the full cached OciImage, including the digest used to detect image changes across runs. When the digest changes the overlay upper layer is reset.

Locking

sandbox/lock contains the running PID. If the lock file exists and the PID is alive, airlock start refuses to start (one VM per project at a time). Stale locks (dead PID) are silently cleared.