Skip to main content

Tenant Offboarding Is a Data-Deletion Problem: The GDPR Erasure Checklist Across Volumes, Snapshots, Logs, Registries, and Backups

13 min readDora NodaDora Noda
Share
On this page

Six months after a tenant churned, the erasure request lands in your inbox. Their namespace is long gone — kubectl delete namespace took ninety seconds back in March. So you reply "done," right?

Not even close. That namespace was the easy 10%. The tenant's personal data is still sitting in seven other places, each with a different owner, a different retention clock, and a different answer to the question "what does deleted even mean here." GDPR Article 17 does not ask whether you deleted the namespace. It asks whether you erased the personal data — everywhere — without undue delay. France's CNIL has sanctioned controllers over erasure failures sixteen times since 2020, and Criteo's €40 million fine in 2023 was explicitly about erasure requests never propagated to every system.

Here is the core deliverable of this post: the per-store checklist, up front. Everything after it is the mechanism and the gotchas behind each row.

StoreOwnerWhat holds personal dataDeletion mechanismRetention clock"Deleted" means what
VolumesPlatform / CSI driverPVCs, PVs, tenant databasesDelete PVC; verify PV reclaim completesImmediate on delete (Delete reclaim)Blocks unmapped; overwritten only by later writes
SnapshotsPlatform / CSI driverVolumeSnapshot + provider snapshotsDelete snapshot objects; force DeletionPolicy: DeleteImmediate, unless policy is RetainProvider-side snapshot destroyed
LogsPlatform / LokiLog lines, labels, tenant IDsCompactor retention + log-entry deletion APIretention_period (e.g. 30d) + retention_delete_delayChunks dropped; index entries expire
RegistryPlatform / registryImages, tags, build cache layersDelete tags/manifests; run GCUntil GC runs; cache is unbounded by defaultBlobs unreferenced, then collected
Velero backupsPlatform / VeleroBackup tarballs, Kopia/Restic snapshotsvelero backup delete; TTL expirySchedule --ttl (e.g. 720h)Objects removed from bucket; pinned snapshots released
DNSPlatform / DNS providerTenant hostnames, ACME recordsDelete records via provider APITTL propagation (minutes–hours)Records stop resolving
MetricsPlatform / Mimir/CortexLabel values, tenant IDs, exemplarsRetention + per-tenant series deletionRetention window (often 15d–1y)Blocks compacted away

Two stores are missing from that table on purpose: the ones you cannot delete at all. Certificate Transparency logs are public and append-only — a tenant's hostname logged there stays logged forever. How to handle that honestly comes near the end.

What Article 17 Actually Demands

Article 17 gives every data subject the right to obtain erasure "without undue delay" when one of its grounds applies: the data is no longer necessary, consent is withdrawn, the subject objects with no overriding interest, or the processing was unlawful. Three things about that sentence matter operationally.

First, "without undue delay" is a clock, not a vibe. Regulators expect a documented timeline from request to completed erasure, including the lagging stores. The accepted practice for backups is to put the data "beyond use" — flagged for deletion, unrestorable to production without a compliance review — until the backup naturally expires. What regulators do not accept is silence: an erasure receipt that says "forgotten" while a restorable copy sits in object storage is a misrepresentation, not a completion.

Second, the right is not absolute, and the exceptions cut both ways. You may keep data you are legally required to retain (invoices under tax law, for instance), but "it was easier to keep the backup" is not an exception. Each store you exclude from erasure needs a named legal basis, not an operational shrug.

Third, enforcement has teeth and it is accelerating. European regulators imposed roughly €1.2 billion in GDPR fines in 2025, up 22% year over year, with cumulative fines past €5.65 billion across more than 2,200 proceedings — and the EDPB ran a 2025 coordinated enforcement action specifically on the right to erasure.

CNIL alone accounts for sixteen erasure sanctions between 2020 and 2025, and the pattern in cases like Criteo (€40M) and TIM (€27.8M) is consistent: the request arrived, then died somewhere between the ticketing system and the sixth datastore. A self-hosted PaaS with seven stores and no propagation checklist is exactly the shape those fines punish.

Store by Store: The Seven Deletion Paths

1. Volumes: the delete you think you already did

Deleting a namespace deletes its PVCs, and with the default Delete reclaim policy the PVs and backing disks follow. That is the path everyone tests. The failure mode is everything that is not default: a StorageClass with reclaimPolicy: Retain (common for production databases, where operators would rather leak a disk than lose data) leaves the PV and its data behind after the PVC vanishes. Your erasure runbook must list every StorageClass in the fleet and state its reclaim policy, because "the PVC is gone" proves nothing about the disk.

Even then, "deleted" for a volume means unmapped, not wiped: blocks are freed for reuse, not zeroed. Unreachable through any API is enough for most threat models — but contracts promising cryptographic erasure of data at rest need per-tenant encryption (see crypto-shredding below), not just deletion.

2. Snapshots: the Retain trap

Volume snapshots are where "deleted" most often turns out to be false. A VolumeSnapshot object has a DeletionPolicy, and when it is Retain — the default on several CSI drivers and snapshot classes — deleting the Kubernetes object orphans the provider-side snapshot instead of destroying it. The snapshot listing in your cloud console or Hetzner-equivalent keeps the tenant's bytes indefinitely while your cluster reports nothing to delete.

The fix, documented in Velero's own CSI snapshot handling: before deleting snapshot objects, set every VolumeSnapshotContent to DeletionPolicy: Delete regardless of the class default. Generalize it into a rule — delete provider snapshots first, verified against the provider API rather than the cluster, then the Kubernetes objects — and enumerate snapshots by tenant label so orphans from a deleted namespace stay findable.

3. Logs: Loki keeps what you forgot to scope

Application logs are personal-data rich — user IDs, emails in error messages, IP addresses in access logs — and Loki's deletion story has two speeds. The slow speed is retention: the compactor enforces limits_config.retention_period (say, 30 days) plus a retention_delete_delay (typically 2 hours) before chunks actually disappear from object storage. Retention without the compactor enabled is a config value that does nothing, which is worth verifying rather than assuming.

The fast speed is Loki's log-entry deletion API, which removes entries matching a stream selector within a time window — but it requires the TSDB index store (BoltDB Shipper support is deprecated and being removed), and it deletes chunk content while index hygiene follows its own schedule. Design for this at ingest time: a per-tenant label turns "delete tenant X's logs" into one API call instead of a regex archaeology project. And one design rule earns its keep here: events that must be deletable on demand belong in a database row you own, with the log carrying only a reference ID — never the payload itself.

4. Registry: images and the build cache nobody expires

Tenant images carry personal data more often than teams admit: seeded demo databases baked into layers, config files with customer hostnames, .env files somebody COPYd in 2024. Deleting tags is step one; step two is garbage collection, since unreferenced blobs persist until GC runs. Step three — the one everyone misses — is the build cache: content-hashed layer caches survive tag deletion entirely, and a cache hit can resurrect a "deleted" layer into a fresh build months later. Scope build caches per tenant so eviction is one operation, and run GC often enough that "deleted tag" converges to "deleted bytes" inside your documented timeline.

5. Velero backups: the store with its own retention clock

Velero backups are the canonical "beyond use" store: a scheduled backup with --ttl 720h0m0s keeps thirty days of tenant data by design, and erasure cannot ethically mean destroying the whole shared backup to surgically remove one tenant. Two mechanics matter here.

First, deletion must go through velero backup delete <name> --confirm, never kubectl delete backup. Deleting the CR alone leaves the objects in the bucket, and backup-sync will cheerfully re-create the CR from storage within minutes — the single most common Velero deletion footgun, and exactly the kind of "we deleted it but it came back" story that reads terribly in front of a regulator.

Second, data-mover snapshots (Kopia/Restic) have their own lifecycle. Velero pins every Kopia snapshot so Kopia-side retention never removes it; only Velero releases the pin at backup expiry. And a documented leak: if the Velero install namespace is excluded from backups, moved snapshots are never recorded for deletion and leak in object storage past TTL indefinitely. Audit that exclusion before promising any backup-erasure timeline.

For the tenant inside a shared, unexpired backup, the honest answers are the documented "beyond use" flag — plus crypto-shredding, which makes the question moot. That is the next section.

6. DNS: fast to delete, slow to forget

DNS records are the simplest row in the table: delete the tenant's hostnames, wildcard entries, and ACME challenge records through your provider API. The only clock is TTL propagation — minutes to hours, not days. The gotcha is inventory, not mechanism: tenant hostnames accumulate across zones, preview deployments, and internal records (tenant-xyz.internal). Keep them labeled or zoned so enumeration is a query, not a treasure hunt.

7. Metrics: labels are personal data too

Long-term metrics storage (Mimir, Cortex, Thanos) keeps tenant IDs as label values, careless high-cardinality labels like user IDs or emails, and exemplars pointing at trace IDs. Multi-tenant backends enforce isolation in software via headers such as X-Scope-OrgID — which is precisely why deletion must be explicit per tenant, never assumed from namespace removal. Use the backend's per-tenant series-deletion API where it exists; otherwise let the documented retention window do the work, after auditing which labels carry identifying values. A retention window nobody can quote is not a retention policy.

The Backups You Cannot Rewrite: Crypto-Shredding

Every store above except backups yields to a well-aimed delete. Backups do not, because a backup is a point-in-time artifact: rewriting history to excise one tenant from a shared, deduplicated, possibly WORM-locked backup chain is either impossible or indistinguishable from corrupting everyone else's restores. This is the problem crypto-shredding exists to solve.

The design is simple to state and unforgiving to retrofit: encrypt each tenant's data with a per-tenant data-encryption key (DEK) at write time, ideally wrapped by a key-encryption key (KEK) in a KMS. Erasure becomes one operation — destroy the tenant's DEK — and every copy encrypted under it becomes permanently unreadable at once: hot storage, cold storage, WAL segments, snapshots, and every backup that used the same key scheme. The bytes remain; the personal data does not, because personal data that no one can ever decrypt is not personal data in any operable sense.

Regulators agree, with conditions. The EDPB, the UK ICO, and France's CNIL all recognize cryptographic erasure as a valid Article 17 route, provided the encryption is state-of-the-art, key destruction is genuinely irreversible, and the process is documented. The EDPB's Guidelines 02/2025 put it plainly: data encrypted with state-of-the-art methods whose keys are securely destroyed "may be considered erased."

The sting is in the timing: the key hierarchy must exist before the first tenant signs up. Crypto-shredding only covers data written under the per-tenant key. A backup taken before you deployed per-tenant encryption is plaintext (or single-key ciphertext) forever, and no key ceremony in 2026 retroactively shreds it. Designing the DEK-per-tenant hierarchy on day one costs a KMS integration and a key-rotation runbook. Auditing six stores after the first erasure request costs an incident, a DPO escalation, and possibly a CNIL-sized lesson. The cheapest erasure is the one your architecture performs by construction.

What You Cannot Delete, and How to Say So

Two artifacts in a typical platform genuinely cannot be erased, and pretending otherwise is worse than disclosing it.

Certificate Transparency logs are public, append-only Merkle-tree ledgers of every issued TLS certificate. Once your platform requested a certificate for tenant-app.example.com, that hostname is in the log forever — no deletion API exists, by design, because the log's security property depends on nobody being able to rewrite it. Fraudulent certificates cannot be removed either; the ecosystem revokes instead. If tenant hostnames are personal data in your context (they can be, for named personal domains), say so in your privacy documentation before issuance, not after the erasure request.

Voluntary WORM locks are the self-inflicted version: an object-locked bucket or immutable audit log you configured yourself has no GDPR carve-out. Regulators distinguish "technically impossible to delete" (CT logs — documented disclosure accepted) from "we chose a retention mechanism and now it is inconvenient" (your WORM lock — your design decision to defend). For genuinely immutable stores, the accepted posture is the documented timeline: flagged beyond use, excluded from any restore-to-production path without compliance review, with expiry or key destruction as the terminal state.

Write both categories into the erasure runbook as explicit rows — "CT logs: cannot delete, disclosed at issuance" — so the first time your DPO hears about them is not during an incident.

Build the Checklist Into the Offboarding Runbook

The through-line of all seven stores is that erasure is a propagation problem, not a deletion problem. Every fine cited in this post — Criteo, TIM, Vodafone Italia — follows the same shape: the request arrived, some systems complied, and the data subject's record survived somewhere nobody checked. The fix is not heroics; it is a runbook that enumerates every store, names each store's owner, mechanism, and clock, and runs the same way on the hundredth erasure as the first.

Concretely: turn the table at the top of this post into an executable checklist — one script or controller per row, each emitting an erasure receipt (what was deleted, when, under which mechanism, with the backup-expiry date for anything beyond use). Gate tenant offboarding on the checklist completing, the way you would gate a deploy on CI. And if you take one architectural decision away from this post, make it the per-tenant key hierarchy: it converts the hardest row in the table from an ongoing audit burden into a single KMS call.

Self-hosting the platform means owning this checklist end to end — there is no upstream provider to forward the erasure request to. That is the job, and it is automatable.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with the machine-readable API surface an erasure runbook (or an AI agent running it) needs. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex