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 downloadThis 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.yamlChecked 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: minimalPodspawn 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.yamlFetched over HTTPS and cached locally. Useful for org-wide bases shared across many repos.
Available bases
| Name | Description |
|---|---|
ubuntu-dev | Ubuntu 24.04 + git, curl, ripgrep, fzf, neovim, jq, htop, make |
minimal | Ubuntu 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 type | Merge behavior |
|---|---|
Scalars (base, shell, mount, mode) | Child wins if non-empty |
packages, extra_commands | Child appends to base, duplicates removed. "!item" removes from base. |
services | Same-name: child replaces the service. Different name: appended. "!name" removes. |
repos | Same-URL: child overrides branch/path. Different URL: appended. "!url" removes. |
env | Merge maps, child wins on key conflict. Empty value "" removes the key. |
on_create, on_start | Concatenate: base runs first, then child |
dotfiles | Child replaces entirely if set (not per-field) |
resources | Per-field: cpus and memory override independently |
ports.expose | Append and deduplicate (no bang-replace support) |
Example
Base (ubuntu-dev):
base: ubuntu:24.04
packages: [git, curl, ripgrep, fzf, neovim, jq]
shell: /bin/bashChild:
extends: ubuntu-dev
packages: [go@1.24]
shell: /bin/zsh
on_create: go mod downloadResult:
base: ubuntu:24.04
packages: [git, curl, ripgrep, fzf, neovim, jq, go@1.24]
shell: /bin/zsh
on_create: go mod downloadMulti-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:
- Create a base Podfile in a shared repo or as a local file
- Reference it from each project's Podfile via
extends - 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?