Self-managed engineering project · Restore evidence plus live OCI inventory verified 24 July 2026
Hybrid-Cloud Kubernetes Disaster Recovery on OCI
A cost-guarded Oracle Cloud recovery system that encrypts a controlled k3s workload off site, restricts the uploader by design, and proves one backup through an isolated restore.
A local cluster and its storage can share the same failure boundary. Moving recovery data off site introduces separate identity, encryption, retention, cost, and dependency risks, while an uploaded object still does not prove that an application can be recovered.
Implemented solution
Terraform defines a compartment-scoped OCI foundation with private Object Storage, a default Vault and software key, restrictive networking, notifications, and a dedicated create/list uploader. Backup confidentiality relies on client-side age encryption before upload; the bucket adds provider-managed encryption rather than a customer-managed key. A hardened Kubernetes job exports selected non-secret resources, creates and validates a PostgreSQL dump, checksums and packages the payload, uploads with no-overwrite semantics, verifies presence, and removes plaintext working data.
Outcome
The OCI foundation was applied and reported converged, one encrypted backup was verified, uploader denial tests passed, and that backup was restored into a separate namespace and fresh volume with deterministic data checks. A read-only tenancy inventory on 24 July 2026 confirmed the private versioned bucket, five lifecycle rules, private subnet, Vault/key/secret metadata, budget and notification topic, with no compute, OKE, load balancer, NAT gateway, or database resources. The synthetic scope, notification delivery, retention lock, hard quotas, independent key custody, Flux ownership, and recurring restores remain incomplete.
Technical and operational context
The project treats disaster recovery as a chain of bounded claims: prevent unintended cloud resources, keep secrets out of state, limit uploader authority, validate the archive before use, prove recovery away from the source volume, and leave unresolved operational dependencies visible.
Constraints
Current recovery evidence covers only a synthetic PostgreSQL workload and selected non-secret Kubernetes resources.
The design targets OCI Always Free resource classes; it does not claim a measured zero-cost billing result.
Recovery compute and OKE remain disabled, and the configuration excludes paid compute, load balancers, NAT gateways, and managed databases.
Backup storage is private and versioned; no public bucket or plaintext upload is permitted.
Recovery-key contents and uploader credentials must stay outside Terraform state and tracked Kubernetes YAML.
Existing cluster backup systems remain separate and unaffected by this validation scope.
Architecture
The data path runs from a controlled k3s workload through allowlisted export, database dump, checksums, deterministic packaging, client-side age encryption, a create/list-only identity, and private OCI Object Storage. Recovery uses separate administrator authority, strict archive validation, and an isolated PostgreSQL target. Terraform owns the cloud foundation; Kubernetes manifests and guarded scripts own workload deployment and secret handoff.
Controlled k3s workload
Allowlisted resource export
PostgreSQL custom dump
Metadata and SHA-256 checks
Local age encryption
Create/list-only upload
Private OCI Object Storage
Isolated restore and data verification
Architecture visual
Validated and implemented paths are separated from planned and externally blocked controls in this public-safe OCI recovery architecture.
The root configuration composes foundation, network, Object Storage, Vault, notifications, and uploader-IAM modules around an existing isolated compartment and common governance tags. Sensitive identifiers are exposed only through sensitive outputs.
Private storage and recovery network
The applied design uses a private versioned Standard-tier bucket with lifecycle and destruction controls, plus a private subnet and service-gateway path that prohibit public instance addressing.
Keys and notifications
A default software-key Vault and key are destruction-protected, while secret contents are inserted outside Terraform. The notification topic exists, but no delivery subscription is claimed without an approved destination.
Compute safety interlocks
Recovery-instance and OKE feature flags default to false and are reinforced by variable validation, Terraform checks, and repository tests; neither resource class is part of the applied foundation.
Cost and resource governance
Budgets warn; they do not enforce
The investigation found an existing budget, but the project does not present it as a spending barrier or as proof of zero billing.
Quotas are a separate enforcement layer
A compartment-scoped quota proposal covers prohibited compute, storage, networking, and database classes. Its syntax is validated, but live application remains review-gated.
Tests prevent unsafe definitions
Static checks reject recovery compute, OKE, load balancers, NAT gateways, databases, public buckets, non-default Vaults, non-software keys, unsafe IAM verbs, and missing lifecycle controls.
Lifecycle and capacity warnings
Object lifecycle rules bound retained daily, weekly, metadata, previous-version, and multipart data. A storage warning threshold supports review but is not presented as hard cost enforcement.
Implementation
Terraform defines a compartment-scoped OCI foundation with private Object Storage, a default Vault and software key, restrictive networking, notifications, and a dedicated create/list uploader. Backup confidentiality relies on client-side age encryption before upload; the bucket adds provider-managed encryption rather than a customer-managed key. A hardened Kubernetes job exports selected non-secret resources, creates and validates a PostgreSQL dump, checksums and packages the payload, uploads with no-overwrite semantics, verifies presence, and removes plaintext working data.
Applied an isolated OCI foundation for private storage, key management, restricted networking, notifications, and least-privilege upload
Executed an encrypted no-overwrite backup of selected Kubernetes configuration and a controlled PostgreSQL database
Restored the verified archive into a separate namespace and fresh persistent volume without reusing the source database
Prepared and remotely verified an immutable Linux amd64/arm64 backup image while leaving the live migration review-gated
Key decisions and trade-offs
Object Storage instead of recovery compute
Off-site ciphertext does not require another always-on VM or cluster. This reduces cost and attack surface, while accepting that recovery compute must be provisioned separately during a real event.
Secret creation outside Terraform
The recovery identity is inserted and checked through guarded scripts so private key material never becomes a Terraform value or state attribute. The trade-off is explicit operator custody and recovery responsibility.
Create-only uploader authority
The scheduled workload can create a unique ciphertext and inspect its metadata, but cannot download, overwrite, delete, or access Vault. Recovery therefore uses separate administrator authority.
Restore as a separate evidence milestone
Backup metadata correctly records that the historical archive was not restored at creation time. A later isolated drill supplies separate evidence without rewriting the original artifact's claim.
Security design
Client-side encryption boundary
The archive is encrypted inside the restricted backup Pod before any OCI upload begins; only the public recipient is tracked, and plaintext workspace data is ephemeral and cleaned on exit.
Least privilege in cloud and cluster
OCI policy limits the uploader to the intended bucket prefix and create/inspect operations. Namespaced Kubernetes RBAC allows only the selected reads and denies Secret access, mutation, role escalation, and impersonation.
Restricted runtime and network
The backup container runs as a non-root user with a read-only root filesystem, dropped capabilities, bounded temporary storage, and egress limited to required cluster, database, and OCI paths.
Fail-closed archive validation
The restore validator accepts only the expected schema and regular-file path set, bounds extraction, requires complete checksum coverage, and rejects traversal, links, devices, duplicates, corruption, and non-empty restore targets.
Backup and restore workflow
Discover the allowlisted workload scope without reading Kubernetes Secret objects
Export selected Kubernetes resources and remove runtime-only metadata
Create a PostgreSQL custom-format dump and inspect its catalogue
Run the controlled data-integrity query and record versioned metadata
Generate and verify SHA-256 checksums for every payload member
Create a deterministic compressed archive
Encrypt the archive locally with an age X25519 recipient
Delete the plaintext archive before upload
Upload the ciphertext under a unique name with no-overwrite semantics
Verify non-zero object presence, then remove the ephemeral workspace
Infrastructure concepts demonstrated
CronJobs and Jobs
Namespaced RBAC
Cilium network policy
Pod Security Standards
ResourceQuota and LimitRange
StatefulSet recovery
Persistent volume isolation
Secret file mounts
Published evidence
Sanitized implementation status
Sanitized live output
live OCI inventory: private/versioned bucket, five lifecycle rules, private subnet
paid runtime: no compute, OKE, load balancer, NAT gateway, or database found
backup: encrypted upload verified
uploader: create/list allowed; read/delete/key access denied
restore: isolated fresh-volume data check passed
notification subscription: not configured
retention lock: not configured
local safety suite and terraform validate: passed
Validation
Checks shown here are project-specific; no portfolio-wide claim is applied automatically.
Read-only OCI inventory on 24 July 2026 found one private versioned bucket with five lifecycle rules, one private subnet, one Vault, two key resources, one active secret, one budget, and one notification topic; no compute or managed Kubernetes resource was present
Applied-foundation report records reviewed plans, successful apply, a zero-drift follow-up plan, and live uploader allow/deny checks
Manual backup evidence records successful dump validation, deterministic data checks, encryption before upload, remote object verification, and plaintext cleanup
Isolated-restore evidence records a fresh namespace and volume, exact schema and data verification, source-workload integrity, negative paths, and cleanup
Current local make test passes ShellCheck, 12 mocked backup paths, 7 archive-validator tests, Terraform safety assertions, Kubernetes and container policies, range checks, quota syntax, recurring-restore contract, and blocker tests
Both Kubernetes overlays render locally and the immutable Linux amd64/arm64 image publication is remotely verified
Provider-backed terraform validate passes after the duplicate region declaration was reconciled; public repository CI now runs validation and the safety suite
Challenges and troubleshooting
Read-only container home failure
The first manual job stopped before export or upload because a CLI tried to create its default home directory. The workload now points HOME into the size-limited ephemeral volume, after which the backup completed.
Identity and CLI compatibility
The OCI identity domain required an email-shaped value and ambient administrator authentication initially overrode uploader tests. A reserved non-routable address and explicit API-key selection made the test boundary deterministic.
Terraform validation regression
A duplicate region declaration caused current validation to diverge from the earlier applied evidence. The provider now consumes the canonical root variables, provider-backed validation passes, and public repository CI guards the repaired path.
Operational dependencies outside automation
Independent offline key custody, an approved notification endpoint, clean Flux ownership, and secure recurring-restore credentials require separate operator or external-system action.
Current result
The project has a deployed private OCI backup foundation, a verified encrypted backup, and a successful isolated restore for the controlled PostgreSQL workload. The 24 July 2026 live inventory agrees with the cost-guarded design and the public repository passes provider-backed Terraform validation and its safety suite. The evidence still does not prove cluster-wide or production recovery.
Evidence and links
Links are limited to public-safe source material and sanitized evidence. Credentials, state, plans, identifiers, private topology, and backup contents are intentionally excluded.
Sanitized isolated-restore, negative-path, data-verification, and cleanup evidence.
Lessons learned
An uploaded ciphertext, a readable archive, and a restored application answer different recovery questions and should remain separate evidence gates.
Least-privilege upload reduces the blast radius of scheduled credentials, but it requires a separate and well-documented recovery identity.
Cost safety needs layered controls: feature gates and tests prevent unsafe definitions, lifecycle rules bound retention, budgets warn, and quotas enforce only after they are actually applied.
Dated execution evidence and current source validation are separate claims; both should be checked before presenting a recovery result.
Remaining work
Run a reviewed zero-drift plan before the next infrastructure change
Complete independent offline key-escrow custody and recovery verification
Configure and prove delivery to an approved notification destination
Review and apply the compartment quota policy only after live tenancy compatibility is confirmed
Migrate the workload to the verified immutable multi-architecture image through clean Flux ownership
Run the suspended recurring restore only after custody, notification, credential, and ownership gates pass
Expand recovery scope deliberately before making any cluster-wide claim and measure recovery objectives only from repeated evidence
Limitations
The completed restore covers one synthetic PostgreSQL workload, selected non-secret manifests, and a fresh persistent volume; it excludes cluster-wide state, Kubernetes Secret values, volume snapshots, and production data.
The design targets Always Free resource classes, but no billing evidence supports a measured zero-cost claim and hard tenancy quotas are not applied.
The notification topic has no observed subscription, and the bucket has lifecycle deletion but no retention rule or WORM lock.
Object Storage uses provider-managed server-side encryption; the Vault exists for an escrow copy of the age identity and is not presented as the bucket encryption key.
The published multi-architecture image has open fixable HIGH findings in upstream client dependencies, and the live workload has not migrated to the verified digest.
Next improvement
Complete independent key custody and notification delivery before enabling the already validated recurring-restore template.