Skip to content

Remote State

By default Kyku writes state to .kyku/state.<env>.json. To share state across machines or CI jobs, set a remote backend in the config file.

export default {
provider: 'aws',
backend: {
type: 's3',
bucket: 'kyku-state',
region: 'us-east-1',
key: 'prod/state.json',
},
resources: [/* ... */],
};
export default {
provider: 'gcp',
backend: {
type: 'gcs',
bucket: 'kyku-state',
key: 'prod/state.json',
},
resources: [/* ... */],
};
export default {
provider: 'digitalocean',
backend: {
type: 'spaces',
bucket: 'kyku-state',
region: 'nyc3',
key: 'prod/state.json',
},
resources: [/* ... */],
};

Spaces reuses the S3 manager with endpoint https://<region>.digitaloceanspaces.com.

export default {
provider: 'hetzner',
backend: {
type: 'http',
address: 'https://state.example.com/prod/state.json',
// lockAddress: 'https://state.example.com/prod/state.lock',
},
resources: [/* ... */],
};

GET retrieves state (404 = empty). PUT writes with If-Match / If-None-Match. The lock is a second JSON object at lockAddress (default ${address}.lock). Auth is optional until the server returns 401: KYKU_HTTP_TOKEN (Bearer) or KYKU_HTTP_USERNAME + KYKU_HTTP_PASSWORD (Basic).

There are no CLI flags for backend settings. plan, apply, and destroy pick the block up from infrastructure.ts (or -c).

  • Load and save target that single object. Writes are conditional (If-Match / If-None-Match on S3 and HTTP, ifGenerationMatch on GCS) so two writers cannot clobber each other.
  • Each successful save increments serial. A write whose in-memory serial no longer matches the remote object is refused.
  • The lock lives at ${key}.lock in the same bucket (s3://…, gs://…, or spaces://…) or at lockAddress for HTTP, with the same 60s heartbeat and token-conditional stale break as the local lock.
  • Missing S3/GCS/Spaces credentials are a hard error. HTTP allows unauthenticated calls until the server returns 401. Kyku does not write local state instead.

kyku state push uploads .kyku/state.<env>.json to the configured object, verifies the round-trip, and renames the local file to .migrated. It refuses if remote state already exists unless you pass --force. The next kyku plan reads from the remote backend.

Plan and apply never auto-migrate. Opt in on plan with --migrate-state (error if remote exists) or --force-migrate (overwrite). There is no backend.autoMigrate and no bare --force. A leftover .migrated file is never re-uploaded.

There is no state pull / state sync, no backend-to-backend move, no bucket create, and no state version browsing.

Terminal window
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=... # temporary / STS creds
# or: AWS_PROFILE, instance role, or AWS_WEB_IDENTITY_TOKEN_FILE
Terminal window
export GOOGLE_CLOUD_PROJECT=...
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
# or: gcloud auth application-default login
# or: KYKU_GCP_WORKLOAD_PROVIDER + KYKU_GCP_SERVICE_ACCOUNT
Terminal window
export DO_SPACES_REGION=nyc3
export DO_SPACES_ACCESS_KEY=...
export DO_SPACES_SECRET_KEY=...
Terminal window
export KYKU_HTTP_TOKEN=...
# or:
export KYKU_HTTP_USERNAME=...
export KYKU_HTTP_PASSWORD=...