podspawnpodspawn

Extending Podfiles

Compose environments by inheriting from base Podfiles.

The extends field lets a Podfile inherit from a base, then customize on top. This avoids duplicating common setup across repos.

Basic usage

extends: ubuntu-dev
packages: [go@1.24, make]
on_create: go mod download

This inherits everything from the ubuntu-dev base (git, curl, ripgrep, fzf, neovim, jq, etc.) and adds Go on top.

Resolution order

The extends value is resolved by trying each source in order until one matches:

Relative or absolute paths to a YAML file on disk.

extends: ./base/podfile.yaml
extends: /absolute/path.yaml

Checked first. Use this for monorepos or local base files that live alongside your project.

A short name that maps to a base in the embedded registry or cached bases.

extends: ubuntu-dev
extends: minimal

Podspawn checks embedded bases first, then ~/.podspawn/cache/podfiles/. Run podspawn init --update to refresh cached bases.

A full path to a YAML file hosted on GitHub.

extends: github.com/myorg/podfiles/go-base.yaml

Fetched over HTTPS and cached locally. Useful for org-wide bases shared across many repos.

Available bases

NameDescription
ubuntu-devUbuntu 24.04 + git, curl, ripgrep, fzf, neovim, jq, htop, make
minimalUbuntu 24.04 + git, curl

See the full list at podspawn/podfiles.

Merge semantics

When a child Podfile extends a base, fields are merged according to their type:

Field typeMerge behavior
Scalars (base, shell, mount, mode)Child wins if non-empty
packages, extra_commandsChild appends to base, duplicates removed. "!item" removes from base.
servicesSame-name: child replaces the service. Different name: appended. "!name" removes.
reposSame-URL: child overrides branch/path. Different URL: appended. "!url" removes.
envMerge maps, child wins on key conflict. Empty value "" removes the key.
on_create, on_startConcatenate: base runs first, then child
dotfilesChild replaces entirely if set (not per-field)
resourcesPer-field: cpus and memory override independently
ports.exposeAppend and deduplicate (no bang-replace support)

Example

Base (ubuntu-dev):

base: ubuntu:24.04
packages: [git, curl, ripgrep, fzf, neovim, jq]
shell: /bin/bash

Child:

extends: ubuntu-dev
packages: [go@1.24]
shell: /bin/zsh
on_create: go mod download

Result:

base: ubuntu:24.04
packages: [git, curl, ripgrep, fzf, neovim, jq, go@1.24]
shell: /bin/zsh
on_create: go mod download

Multi-level extends

Extends chains are resolved recursively:

# grandparent.yaml
base: ubuntu:24.04
packages: [git]

# parent.yaml
extends: ./grandparent.yaml
packages: [go]

# child podfile.yaml
extends: ./parent.yaml
packages: [npm]
# result: packages = [git, go, npm]

Maximum chain depth is 10. Circular references are detected and rejected.

Building org-wide bases

For organizations with many repos sharing common tooling:

  1. Create a base Podfile in a shared repo or as a local file
  2. Reference it from each project's Podfile via extends
  3. Changes to the base automatically propagate (image hash changes trigger rebuilds)
# In each project
extends: github.com/myorg/podfiles/go-base.yaml
packages: [project-specific-tool]

How is this guide?

On this page