The heavy tier¶
Most real repos run their integration tests with docker compose or Testcontainers, and build with more than the
default 2 CPUs and 4 GiB.
A heavy sandbox is a VM of its own with Docker running natively inside it. So
docker build, docker compose up and Testcontainers work as on a laptop, and the containers it starts stay inside
that VM. The heavy tier also holds the bigger resource profiles. gVisor stays the default tier for everything else.
This page turns the tier on and says what decides whether a sandbox gets it. How Docker runs inside is on Docker in the sandbox, and why the VM holds on heavy-tier isolation.
# envs/python-docker.yaml (an admin's environment)
name: python-docker
extends: python
heavy: true # or `tier: heavy`: Docker in the image and in the sandbox
profile: large # standard (2 vCPU / 4 GiB) | large (8 / 32 / 100 GiB disk) | xlarge (16 / 64 / 200)
# .hangar/environment.yaml (the repo's)
heavy: true # a request, off unless an admin allows it; see "What decides" below
services:
deps: {compose: docker-compose.yml} # a Compose stack the supervisor runs (heavy only)
db: {compose: ops/compose.test.yml, port: 5432} # ready once 5432 answers
cache: docker-compose.cache.yml # short for {compose: docker-compose.cache.yml}
Turning it on¶
The tier is off until an admin lists providers for it, as for fast and isolated:
HANGAR_TIERS='{"fast": ["e2b"], "isolated": ["kubernetes"], "heavy": ["kubernetes"]}'
A heavy sandbox only ever runs on a provider listed there, in order, falling back past one that is full or down. It
never runs anywhere else: like isolated, it is a security choice (a privileged sandbox needs a VM around it).
| Provider | What a heavy sandbox is | Setup |
|---|---|---|
kubernetes |
a Kata pod (HANGAR_HEAVY_RUNTIME_CLASS, kata-qemu-runtime-rs) on the sandboxes-heavy pool |
Running on GKE: the pool, kata-deploy, and the Agent Sandbox controller caveat |
e2b |
an E2B or CubeSandbox microVM | the microVM tier |
docker |
a privileged runc container: root on the host | HANGAR_HEAVY_DOCKER_PRIVILEGED=true, for CI and throwaway machines only |
What decides¶
Before a sandbox starts (heavy.decide), hangar settles Docker, the profile, the caps and the warm pools.
Docker is deny by default. It comes from the environment (heavy: true, tier: heavy; admin fields). Else it
comes from the repo's policy (hangar repo set acme/app --heavy on). Else the repo's .hangar/environment.yaml can
ask for it (as its default branch was last seen), which is only a request.
A request is honored when an admin has allowed it for that repo (hangar repo set acme/app --heavy allow) and this
hangar has a heavy tier. Unset, or --heavy deny, it isn't. Otherwise anyone who can push the default branch could
give the repo's sandboxes root in a VM on costlier nodes.
A heavy repo on a plain environment gets that environment's heavy variant: the same spec, its image built with Docker in it. The repo's builds then start from that variant too.
A request that isn't allowed runs on the default tier, without Docker, and says so plainly:
- a warning on the sandbox (
get_sandbox'sinfo.warnings, the job's events); - a "Docker isn't enabled for this repo" section in the agent's instructions that gives the admin command;
- a
WARNonhangar-agent env doctor'stierline:
WARN tier tier standard (2 CPUs, 4 GiB, gvisor); Docker isn't enabled for acme/app: its .hangar/environment.yaml
asks for the heavy tier, and a repo's own file only requests it, and an admin hasn't allowed that for
this repo, so this sandbox runs on the default tier, without Docker. An admin enables it: hangar repo
set acme/app --heavy allow
Profile. First comes a job's label hangar.profile (hangar job submit --profile large, or labels on
POST /api/jobs and create_sandbox), then the repo's file. Each is allowed up to the policy's max_profile, else no
more than the policy's or the environment's own profile. Then come the policy's profile, then the environment's.
large and xlarge run on the heavy tier's nodes, with Docker or not: the gVisor pool's nodes can't hold them. A
profile replaces resources. Asking past what's allowed fails the create and says which hangar repo set raises it.
Caps. One person holds at most HANGAR_HEAVY_PERSON_MAX live sandboxes per profile (3 large or larger, 1
xlarge). The accounting is the same per person as for the other caps (team scale).
Work no person owns shares one team bucket with the same caps, so it can't take the heavy pool either. That is jobs from GitHub, Slack or Jira senders linked to nobody, webhooks, and schedules without a person.
Repo builds are hangar's own, one per repo and environment at a time, and aren't counted. Docker at the standard size isn't capped beyond the usual sandbox caps.
Warm pools of heavy environments stay at 0 unless HANGAR_HEAVY_WARM_POOLS=true: a heavy node held warm costs
(see team scale).
heavy, tier: heavy and a profile other than standard are admin fields on an environment, like runtime_class.