Port band allocation
Eleven ports per project, chosen by formula, persisted, and checked before a stack starts.
What it is
A port band is the contiguous range of host ports reserved for one project. The allocator uses a fixed base of 60000 and a block size of 11, with a fixed offset for each of the eleven Supabase services. A project at slot n owns every port from 60000 + 11n to 60000 + 11n + 10.
The allocation is deterministic and persisted. It is not a search for free ports at start time. A reserved block belongs to the project until the project is deleted; released slots become reusable, and the allocator prefers the first never-used slot so the audit trail stays readable.
Why it exists
Supabase services use several ports per stack. Running two stacks with the CLI's defaults produces a collision that is unpleasant to debug: one stack starts, the other half-starts, and the error names a container rather than the port. A reserved block makes collisions impossible between managed projects, and writing the values into config.toml makes the CLI use them.
The 60000 band
Diagram: the 60000 band divided into blocks of eleven ports, with slot zero at 60000 to 60010 and slot one at 60011 to 60021; each block lists gateway, database, shadow, Studio, mail, SMTP, POP3, analytics, pooler, edge runtime and edge inspector.
A worked example
Take a fresh project that receives slot 0. The allocator computes eleven ports:
- api =
60000 + 0 × 11 + 0= 60000 - db =
60000 + 0 × 11 + 1= 60001 - shadow = 60002, studio = 60003, inbucket = 60004, smtp = 60005
- pop3 = 60006, analytics = 60007, pooler = 60008
- edge_runtime = 60009, edge_inspector = 60010
A second project receives slot 1 and therefore 60011 through 60021. A third receives 60022 through 60032. The arithmetic is executed by the allocator itself; these values are what the formula returns for those slots.
The important property is that the arithmetic is repeatable. Given the same slot, the allocator returns the same eleven ports on every start, and the project's configuration is rewritten to match. That is what lets a saved client configuration survive a restart, as long as the allocation itself does not change.
Persisted allocations
Diagram: the port_allocations table stores one row per slot with eleven port columns, a unique slot, a unique Supabase project id and a released timestamp; released rows keep their history while their ports become reusable.
Allocation, reuse and legacy projects
The allocator reads every allocation and treats a slot as occupied if any row has ever used it, because the slot column is unique. Ports are considered occupied only for active rows: a soft-released row keeps its history but its ports can be reused. When a released row's slot is chosen again, the row is reclaimed in place, which keeps the unique slot constraint satisfied and preserves the rest of the history.
Imported projects that already ran with the CLI's default ports keep those values as a legacy allocation. The legacy range starts at 54321 and is recorded per project, because a project's existing configuration may already use a non-default block. A managed start later moves the project into the 60000 band.
Failure and recovery
Preflight checks the project's ports before a start. If a port is held by another project, the start is refused with the owner named. If it is held by an unrelated host process, the start is refused too; the manager adopts its own leftover containers but not someone else's process. A crashed session can leave containers behind, and the orphan reconciliation releases allocations whose project is gone or whose containers no longer exist.
The example environment file documents PORT_BAND_BASE and PORT_BLOCK_SIZE, and the setup script validates their presence, but the running allocator does not read them. Changing those values does not change allocation. The constants are the source of truth.
Configuration
supabase/config.toml before a managed start.