Wiring together¶
Let's build something real — a FastAPI application on Python 3.14, backed by PostgreSQL. Three jails, each doing one thing, all wired together.
The config¶
The complete project files are in the repo examples.
Create a file called stack.ucl:
jail "python-314" {
setup {
python { type = "ansible"; file = "playbooks/python-314.yml"; }
}
}
jail "postgres-16" {
setup {
postgres { type = "ansible"; url = "hub://postgres/16"; }
}
forward {
pg { host = 6432; jail = 5432; }
}
}
jail "fastapi-314" {
base { type = "jail"; name = "python-314"; }
depends ["postgres-16"]
setup {
fastapi { type = "ansible"; file = "playbooks/fastapi-314.yml"; }
}
forward {
http { host = 8080; jail = 8000; }
}
mount {
src { host = "."; jail = "/srv/app"; }
}
exec {
uvicorn {
cmd = "python3.14 -m uvicorn app:app --reload --host 0.0.0.0";
dir = "/srv/app";
healthcheck {
test = "fetch -qo /dev/null http://127.0.0.1:8000";
interval = "30s";
timeout = "10s";
retries = 5;
}
}
}
}
Bring it up¶
Launch the interactive shell and select up, then pick stack.ucl:

What's happening¶
Jailrun uses Ansible for provisioning — every setup block points to a playbook that runs when the jail is first created.
The hub:// scheme tells jrun to pull the playbook from Jailrun Hub — a curated collection of playbooks for common services like PostgreSQL, Redis, Nginx, and more. You can mix remote playbooks with your own local ones, composing layer by layer. In this config, postgres-16 uses a Jailrun Hub playbook while python-314 and fastapi-314 use local ones.
Compiling from source can be slow. You do it once in python-314, then fastapi-314 is created as its clone via the base block — a fully independent copy, ready instantly and using no extra disk space until it diverges.
Deploy order is controlled by depends. Jailrun resolves the dependency graph automatically. In this case: python-314 first (it's the base), then postgres-16 (it's a dependency), then fastapi-314 last.
Jails discover each other by name. From inside fastapi-314, try ping postgres-16.local.jrun — it just works. You can use fully qualified jail hostnames directly in your app's database config.
Port forwarding works from your host. PostgreSQL is reachable at localhost:6432. FastAPI at localhost:8080. Healthchecks are built in — the process supervisor monitors each service and restarts it if the check fails.
Live reload works out of the box. Your project directory is shared into the jail. Uvicorn's --reload sees file changes instantly.
Check status¶
Select status from the shell:

Inspect and debug¶
Select ssh and pick a jail to drop into:

Or select cmd to run a one-off command without opening a shell:

Update and tear down¶
You can tear down any jail at any time with down — it removes the jail and cleans up its mounts, ports, and DNS entries without affecting the rest of the stack:

If you change the config, run up again. It won't recreate jails from scratch if they exist already, only the parts that differ from the current state will be applied.
Tip
See CLI reference for the full list of commands.