Data residency
The problem
Section titled “The problem”A deployment is sometimes required — by contract, by a regulator, or by a tenant’s own policy — to run inside a particular country. Somebody has to make that requirement a property of the deployment rather than a note in a runbook, and it has to be checked, in both directions: the requirement has to be well-formed, and the machine it lands on has to actually be where it says it is.
The hard part is not the check. It is what happens when the requirement is written down wrongly. A constraint expressed as free text is not rejected when it is misspelled — it simply matches nothing and constrains nothing, silently, on the one deployment whose whole purpose was the constraint.
What it enforces: placement
Section titled “What it enforces: placement”Residency decides which machine runs the container. Nothing else.
You declare it in the deployment’s placement block:
apiVersion: odysseus/v1kind: Deploymentmetadata: name: ledger-apispec: image: registry.example.com/ledger-api:1.4.0 replicas: 2 placement: residency: country: CAThat says: run this only on a node that reports itself as being in Canada. A region — a
name the control plane you are talking to declares — is the alternative when the
jurisdiction spans several countries, and blockedCountries states the opposite kind of
rule, an exclusion. Every key, its type, its default and the rejection each one raises are
in the deployment reference.
The declaration is checked when you write it. A country must be an assigned ISO 3166-1 alpha-2 code, and a region must be one this control plane declares — anything else is refused at the point of writing, with the value quoted back and the accepted form spelled out. That is the whole point of it being a typed field instead of a label.
Setting the underlying labels by hand is refused, not deprecated. The scheduler reads
three labels — odysseus.io/data-residency-country, odysseus.io/data-residency-region
and odysseus.io/blocked-countries — and the typed block compiles into exactly those. But
writing them yourself is rejected outright, because a label is free text and nothing checks
its value. A misspelled label is not a rejected constraint; it is no constraint, applied
quietly. The rejection prints your own values back to you in the accepted form, so the fix
is a copy and paste.
A node’s country is a fact the node itself reports. Each agent advertises
odysseus.io/country and odysseus.io/geo-region from its own configuration. There is no
API by which the control plane can assert where a machine is — that would make residency a
claim rather than a property of the machine.
When nothing matches, the deployment stays pending. It is never placed somewhere else as a fallback. The scheduler’s refusal names the country you required and the country the node reported, so a mismatch is visible rather than inferred. If nothing is ever labelled to match, see a deployment never leaves scheduling.
What it does not enforce: egress
Section titled “What it does not enforce: egress”A deployment placed in Canada can still call a service anywhere in the world.
This is the sentence to take away from this page. Residency here constrains placement and only placement. The platform does not inspect, filter, proxy or block a container’s outbound network traffic on the basis of the residency you declared, and declaring residency does not create a network policy of any kind. A container running on a Canadian node is free to open a connection to an API in another country, write to a database in another country, or send its telemetry anywhere its image is configured to send it. If that matters for your obligations, it is your image and your network design that have to answer for it, not this field.
Four more things it does not do, for the same reason — they are outside what a placement filter can decide:
- It does not move data that is already somewhere else. Residency applies from the moment a container is placed; it says nothing about volumes, object storage or databases that live outside the node.
- It does not govern where the platform keeps its own records. The deployment specification, its events and its audit trail are held by the control plane, wherever that runs. Residency constrains your containers, not the platform’s own store.
- It does not constrain where an image comes from. The image is pulled from whatever registry the deployment names.
- It is not an attestation. A node reports its own country. Residency is exactly as trustworthy as the configuration of the machines you enrolled, and no geolocation lookup is performed to second-guess it.
Regions are declared by each control plane, not by the product
Section titled “Regions are declared by each control plane, not by the product”A region is an operator’s grouping of countries — a name, a description and the list of
countries it covers — declared in the control plane’s own configuration under
scheduler.geo_awareness.regions. There is no fixed product-wide list, so the names that
are legal here are not necessarily legal on another control plane.
Ask the one you are talking to: GET /api/v1/scheduler/regions returns the regions it
declares and, in the same answer, whether residency enforcement is switched on at all. A
region is satisfied by a node whose odysseus.io/geo-region label names it, or by a node
whose country is one of that region’s countries.
A constraint that cannot be honoured is declared, not hidden
Section titled “A constraint that cannot be honoured is declared, not hidden”Two situations accept the deployment and warn, rather than refusing it:
- Enforcement is switched off on this control plane. The constraint is stored and is not applied; the response says so and names the setting that would turn it on. An operator has to be able to declare intent before enablement, so this is not a refusal.
- No node the tenant can reach advertises a matching label. The response says which country and region values the tenant’s nodes actually advertise — values only, never which node — so the gap is visible immediately rather than after the deployment has sat unplaced. A tenant has to be able to declare residency before an operator labels the machines, which is the order these things really happen in.
Neither is silent, and neither is a quiet success. The refusal at placement time still happens; the deployment simply waits.
Limits worth knowing before you rely on it
Section titled “Limits worth knowing before you rely on it”- Enforcement is off unless the control plane switched it on. The geographic filter is
only added to the scheduler when
scheduler.geo_awareness.enabledis true. With it off, a residency declaration is stored and ignored — which is why the response tells you. - A blocked country is not evaluated when a region requirement has already been
satisfied. The filter answers
regionfirst and stops there, so pairingregionwithblockedCountriesdoes not carve an exception out of that region. Express an exclusion withcountry, or by not declaring the region that contains it. - The platform-imposed variant is not reachable yet. The scheduler reads
…-country-enforcedand…-region-enforcedlabels, by which a platform operator could impose a residency a tenant cannot remove. Nothing sets them today, and the tenant-facing API cannot.
Not this
This page does not list the accepted values, defaults or rejection codes for each key — those are generated into the deployment reference and rejections and alterations, so they cannot drift from the code.
It is also not a compliance statement. It describes a placement constraint and its limits; whether that constraint satisfies a particular obligation is a question about the obligation, and this page deliberately does not answer it.
Where to see it
Section titled “Where to see it”- The keys and their rules: deployment manifest.
- How placement is decided in general: placement and allocation.
- Why a residency declaration was refused, and what to write instead: rejections and alterations.
- A deployment that was accepted and never placed: a deployment never leaves scheduling.