This guide shows how to migrate an existing Docker Compose based Stellar deployment to Kubernetes with the Stellar-K8s operator. It is written for teams currently running one or more Compose services such as stellar-core, horizon, soroban-rpc, and PostgreSQL, and who want an operator-managed, production-ready deployment model.
The guide is intentionally migration-focused:
- It starts with planning and inventory work before any cutover.
- It uses repo-native
StellarNoderesources instead of hand-written Deployments. - It includes data migration, rollback, and validation procedures.
- It includes example Compose stacks and starter manifests under
examples/migrations/docker-compose/.
Use this guide when your current deployment looks like one or more of the following:
- A single Compose file running
stellar-corewith a bind mount or named volume. - A Compose stack running
stellar-core,horizon, andpostgres. - A Compose stack running
soroban-rpcthat points to a local or remote validator. - A development stack that stores secrets in
.envfiles or inline Compose environment blocks.
This guide does not attempt a one-click migration for every Compose feature. In particular, depends_on, host networking, custom sidecars, and ad hoc shell entrypoints still require manual review after conversion.
After migration, the Compose concerns below map to operator-managed Kubernetes resources:
| Docker Compose concept | Kubernetes / operator target |
|---|---|
services.stellar-core |
StellarNode with spec.nodeType: Validator |
services.horizon |
StellarNode with spec.nodeType: Horizon |
services.soroban-rpc |
StellarNode with spec.nodeType: SorobanRpc |
| bind mounts / named volumes | spec.storage PVCs or snapshot-based bootstrap |
| inline environment secrets | Kubernetes Secret or validatorConfig.seedSecretSource |
depends_on |
Kubernetes readiness, service discovery, and reconciliation |
ports |
ClusterIP service, optional ingress or loadBalancer |
| process restarts | operator reconciliation and rollout strategy |
Do not start by rewriting YAML. Start by building a migration worksheet for the current Compose deployment.
Capture the following for every Compose service:
- Service name and image tag.
- Node role: Validator, Horizon, Soroban RPC, PostgreSQL, or helper.
- Persistent data location and size.
- Environment variables and secret sources.
- Published ports and external consumers.
- Startup ordering assumptions from
depends_on. - Health checks and restart policies.
Recommended worksheet columns:
| Service | Role | Image | Data path | Secret inputs | External ports | Replacement |
|---|---|---|---|---|---|---|
stellar-core |
Validator | stellar/stellar-core:21.0.0 |
/var/lib/stellar |
STELLAR_CORE_SEED |
11625, 11626 |
StellarNode/Validator |
horizon |
Horizon | stellar/horizon:2.30.0 |
none or app cache | DATABASE_URL |
8000 |
StellarNode/Horizon |
postgres |
Database | postgres:16 |
/var/lib/postgresql/data |
POSTGRES_PASSWORD |
none | managed or external DB |
Choose these target-state decisions before cutover:
- Namespace per environment, such as
stellar-mainnetorstellar-testnet. - Storage class for ledger data and database data.
- Secret source:
- local Kubernetes Secret for development
- External Secrets / Vault / CSI-backed source for production
- Exposure model:
- internal only
ingressloadBalancer
- Database strategy for Horizon:
- keep existing external PostgreSQL and reference it with
horizonConfig.databaseSecretRef - move to an operator-managed database with
spec.managedDatabase
- keep existing external PostgreSQL and reference it with
Write down clear migration success criteria:
- Validator reaches expected ledger progression on the target network.
- Horizon returns healthy responses and ingests from the correct validator.
- Soroban RPC can reach captive core / upstream core successfully.
- Secrets are no longer stored in Compose or
.envfiles. - Data volumes are durable and retained on deletion.
- Rollback can restore service to the original Compose deployment within the agreed RTO.
There is no automated Compose-to-YAML converter in this repository. Map each Compose service
manually to a StellarNode (or external database Secret) using the canonical examples:
| Compose service pattern | Canonical example manifest |
|---|---|
stellar-core / validator |
examples/validator-testnet.yaml or examples/validator-mainnet.yaml |
horizon |
examples/horizon.yaml |
soroban-rpc |
examples/soroban-rpc.yaml |
| Validator + Horizon stack | examples/migrations/docker-compose/docker-compose.validator-horizon.yml (reference Compose layout) |
For each service, copy the closest example, then adjust:
metadata.namespace,metadata.name, andspec.networkspec.storagesize, class, and retention policyspec.resourcesrequests and limits- Secret references (
validatorConfig.seedSecretRef,horizonConfig.databaseSecretRef) - Exposure (
ingress,loadBalancer, or internal-only Services)
Annotate each manifest with the source Compose service name (for example
metadata.annotations.migrated-from-compose-service: stellar-core) so reviewers can trace the mapping.
environmentblocks as mappings orKEY=valuelists → KubernetesSecret+ CRD field refs.- Compose
deploy.replicas→spec.replicas(Horizon / Soroban RPC only). - Compose CPU and memory hints from
deploy.resources→spec.resources. - Common data-volume mount paths such as
/var/lib/stellar→spec.storagePVCs. - Common service naming patterns such as
validator,stellar-core,horizon, andsoroban-rpc.
depends_on- host networking
- custom entrypoints and wrapper scripts
- TLS termination and ingress hostnames
- exact storage sizing
- exact history archive, quorum, and database tuning
After drafting manifests from the canonical examples, review each manifest against the API reference in docs/api-reference.md.
Typical Compose validator concerns should map to these operator fields:
| Compose concern | StellarNode field |
|---|---|
| Stellar Core service | spec.nodeType: Validator |
| image tag | spec.version |
| validator seed | spec.validatorConfig.seedSecretRef or seedSecretSource |
| ledger volume | spec.storage |
| history archives | spec.validatorConfig.historyArchiveUrls |
| resource limits | spec.resources |
| anti-affinity | spec.podAntiAffinity |
| Compose concern | StellarNode field |
|---|---|
| Horizon service | spec.nodeType: Horizon |
| database connection string | spec.horizonConfig.databaseSecretRef |
| upstream core URL | spec.horizonConfig.stellarCoreUrl |
| ingestion toggle | spec.horizonConfig.enableIngest |
| worker count | spec.horizonConfig.ingestWorkers |
| published API endpoint | spec.ingress or spec.loadBalancer |
| Compose concern | StellarNode field |
|---|---|
| Soroban RPC service | spec.nodeType: SorobanRpc |
| upstream core URL | spec.sorobanConfig.stellarCoreUrl |
| captive core config | spec.sorobanConfig.captiveCoreStructuredConfig |
| scaling needs | spec.autoscaling |
Compose often runs PostgreSQL as an adjacent container. In Kubernetes you should choose one of these patterns:
- Keep the database external and store the connection string in a Secret referenced by
spec.horizonConfig.databaseSecretRef. - Use
spec.managedDatabaseif you want the operator ecosystem to manage PostgreSQL for you. - Restore existing PostgreSQL data into a managed platform database before cutover.
Avoid lifting a Compose PostgreSQL container into Kubernetes unchanged unless it is strictly temporary for migration testing.
Data migration is the highest-risk part of a Compose-to-Kubernetes move. Split it into validator data, Horizon database data, and secret migration.
Choose one of these paths:
- Fresh sync:
- simplest path for testnet or low-urgency environments
- create the
StellarNodewith empty PVCs and allow it to catch up from history archives
- Backup archive bootstrap:
- export the existing ledger data into a compressed archive
- host it on
https://ors3:// - configure
spec.storage.snapshotRef.backupUrl
- Snapshot-based bootstrap:
- if the source data already exists in Kubernetes storage, use
restoreFromSnapshot - see
docs/volume-snapshots.md
- if the source data already exists in Kubernetes storage, use
Example archive-based bootstrap:
spec:
storage:
storageClass: "ssd-premium"
size: "500Gi"
retentionPolicy: Retain
snapshotRef:
backupUrl: "s3://stellar-migration/validator-mainnet-ledger.tar.zst"
credentialsSecretRef: "migration-backup-creds"Before taking the validator copy:
- Stop write-heavy maintenance jobs.
- Record the current ledger sequence.
- Capture checksums or a file inventory for later validation.
- Keep the original Compose volume intact until cutover is accepted.
For Horizon, prefer a logical export/import or managed database migration rather than a raw container copy.
Suggested sequence:
- Freeze schema-changing maintenance.
- Run
pg_dumporpg_dumpallfrom the Compose deployment. - Restore into the target PostgreSQL endpoint.
- Create a Kubernetes Secret containing
DATABASE_URL. - Point
spec.horizonConfig.databaseSecretRefat that Secret. - Validate row counts and recent ledger ingestion.
If the source database is large, run the restore ahead of cutover and keep it close to current with a planned final sync window.
Never move Compose secrets by copying .env files into Git.
Instead:
- Move validator seeds into Kubernetes Secrets for development only.
- For production, use
spec.validatorConfig.seedSecretSource. - Move database URLs, API keys, and backup credentials into Secrets.
- Rotate credentials after the migration completes.
See docs/secret-management-kms.md and docs/secret-rotation.md.
Use a staged migration, not a direct replacement.
Create a dry-run namespace first:
kubectl create namespace stellar-migration
kubectl label namespace stellar-migration stellar.org/network=testnet
kubectl apply -f migrated/manifests.yamlValidate:
StellarNoderesources reconcile successfully.- PVCs bind to the expected storage class.
- Secrets mount correctly.
- Validators progress beyond startup.
- Horizon and Soroban can resolve upstream core services.
Before traffic cutover:
- Keep Compose running as the current production path.
- Run Kubernetes workloads in parallel.
- Compare health endpoints, ledger progression, API responses, and logs.
- Confirm new persistent volumes are stable.
Suggested sequence:
- Announce a migration window.
- Stop write traffic or move to read-only mode where possible.
- Perform final validator archive export or final database sync.
- Update Secrets with final connection details.
- Confirm Kubernetes pods are healthy.
- Switch DNS, ingress, or client routing to Kubernetes endpoints.
- Watch the environment closely for at least one full validation window.
Plan rollback before starting migration. The safest rollback is usually traffic reversion, not reverse data sync.
Trigger rollback if any of the following occurs:
- Validator fails to progress on the correct network.
- Horizon ingests the wrong database or wrong validator source.
- Soroban RPC cannot serve production traffic reliably.
- Secret injection fails.
- PVC performance is materially worse than the Compose baseline.
- Keep the original Compose deployment and volumes untouched until migration sign-off.
- Do not destroy the original Compose database until post-cutover validation completes.
- Repoint DNS or ingress back to Compose endpoints.
- Scale Kubernetes-facing traffic to zero or remove external exposure.
- Preserve Kubernetes logs, events, and manifests for analysis.
- Record the exact cutover and rollback timestamps.
- If Kubernetes accepted new write traffic, decide whether that data must be replayed into the Compose environment before rollback.
- If replay is not feasible safely, prefer a short maintenance window over split-brain writes.
- Never run two active writers against the same Horizon database without a deliberate replication design.
Use this checklist before declaring the migration complete.
[ ] Namespace created and labelled with stellar.org/network
[ ] Secrets exist and do not contain placeholder values
[ ] PVCs are bound to the expected storage class
[ ] Validator reaches expected ledger progression
[ ] Horizon can connect to its database and upstream core
[ ] Soroban RPC can reach upstream core and serve requests
[ ] Network policies allow required traffic and block unintended paths
[ ] Ingress or service exposure matches the target design
[ ] Old Compose endpoints remain available for rollback until sign-off
[ ] Backup / restore evidence captured for migrated data
[ ] Runbooks updated with new Kubernetes endpoints and commands
Example checks:
kubectl get stellarnodes -n stellar-testnet
kubectl describe stellarnode validator-testnet -n stellar-testnet
kubectl get pods,pvc,svc -n stellar-testnet
kubectl logs -n stellar-system -l app.kubernetes.io/name=stellar-operator --tail=100Review:
status.phase- readiness conditions
status.ledgerSequencestatus.ledgerUpdatedAt- service reachability from peer workloads
For validator data:
- compare the last known source ledger sequence to the recovered target
- compare file counts or checksums if an archive migration was used
For Horizon data:
- compare row counts on high-value tables
- compare latest ledger and transaction records
- compare representative API responses before and after cutover
These are the most common migration mistakes when moving from Compose to the operator.
Compose startup ordering is not equivalent to Kubernetes readiness. Replace it with:
- correct service URLs
- readiness probes
- patience for reconciliation and startup
Do not translate Compose environment variables directly into plain-text CRD fields if the value is sensitive. Use Secrets or seedSecretSource.
Compose bind mounts often hide real data growth. Explicitly size Kubernetes PVCs based on:
- current used bytes
- expected annual growth
- restore scratch space
Use Retain during migration windows for safer rollback.
If you move mainnet and testnet workloads into a shared namespace, you lose an important safety boundary. Follow docs/network-isolation.md.
Do not treat Horizon database data like application config. Use a deliberate database migration method and validate it independently.
Published host ports in Compose do not automatically become production-safe Kubernetes exposure. Review ingress, loadBalancer, TLS, and authentication separately.
The repository includes example artifacts:
- Input Compose file:
examples/migrations/docker-compose/docker-compose.validator-horizon.yml - Converted output:
examples/migrations/docker-compose/converted-stellarnodes.yaml
Source pattern:
- one
stellar-corevalidator - one
horizonAPI service - one
postgresservice
Target pattern:
- one Validator
StellarNode - one Horizon
StellarNode - one Secret for validator seed
- one Secret for
DATABASE_URL - PVC-backed storage for validator data
Source pattern:
- one Compose validator service
- seed in
.env - bind mount for ledger data
Target pattern:
- one Validator
StellarNode - one local Kubernetes Secret for the seed
- one retained PVC
This repository includes a video-ready tutorial script at docs/docker-compose-migration-video-tutorial.md.
Use it to record:
- a short planning walkthrough
- a manifest drafting walkthrough from canonical examples
- a manifest review
- a dry-run deployment demo
- cutover and rollback discussion
If you want the shortest safe path, use this order:
- inventory the Compose stack
- choose namespace, storage, and secret strategy
- draft
StellarNodemanifests from the canonical examples - review and harden the manifests
- migrate secrets
- migrate validator and database data
- validate in a dry-run namespace
- run Compose and Kubernetes in parallel
- cut over traffic
- hold the old stack for rollback until acceptance completes
examples/migrations/docker-compose/docs/api-reference.mddocs/network-isolation.mddocs/volume-snapshots.mddocs/backup-verification.mddocs/secret-management-kms.mddocs/secret-rotation.mdexamples/validator-mainnet.yamlexamples/validator-testnet.yamlexamples/horizon.yamlexamples/soroban-rpc.yaml