Skip to content

Data residency

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.

Residency decides which machine runs the container. Nothing else.

You declare it in the deployment’s placement block:

apiVersion: odysseus/v1
kind: Deployment
metadata:
name: ledger-api
spec:
image: registry.example.com/ledger-api:1.4.0
replicas: 2
placement:
residency:
country: CA

That 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.

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.enabled is 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 region first and stops there, so pairing region with blockedCountries does not carve an exception out of that region. Express an exclusion with country, or by not declaring the region that contains it.
  • The platform-imposed variant is not reachable yet. The scheduler reads …-country-enforced and …-region-enforced labels, 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.