Skip to content

Custom Config

Every Kyku resource accepts an optional customConfig field: a plain object of provider-native values that get merged directly into the underlying cloud API request. It’s the escape hatch for the long tail of provider-specific options Kyku’s portable resource shape doesn’t (and won’t) model as first-class fields.

import { Vpc } from '@kykucloud/types'
import type { AwsVpcCustomConfig } from '@kykucloud/aws'
const vpc = new Vpc({
id: 'main-vpc',
name: 'main',
cidr: '10.0.0.0/16',
region: 'us-east',
customConfig: {
InstanceTenancy: 'dedicated',
} satisfies AwsVpcCustomConfig,
})

A resource like Vpc or Vm has a small set of portable, first-class fieldsname, cidr, instanceType, image, network, and so on — that work identically across every provider. customConfig is the opposite: provider-native fields, shaped exactly like that provider’s own create/update request, that only make sense for the provider you’re deploying to.

Kyku merges customConfig directly into the request it sends to the cloud API. It doesn’t reinterpret or rename anything: values use the provider’s own field names and casing (CidrBlock for AWS, ip_range for Hetzner/DigitalOcean, ipCidrRange for GCP), because the whole point is to reach fields Kyku’s abstraction doesn’t cover.

Each provider package exports typed aliases so you get autocomplete and a type error instead of a typo silently doing nothing:

Provider Source Example
AWS @aws-sdk/client-* command input types AwsVpcCustomConfig, AwsVmCustomConfig (from @kykucloud/aws)
GCP @google-cloud/compute request/resource types GcpVpcCustomConfig, GcpVmCustomConfig (from @kykucloud/gcp)
Hetzner Generated from the official cloud.spec.json OpenAPI document HetznerVpcCustomConfig, HetznerVmCustomConfig (from @kykucloud/hetzner)
DigitalOcean Generated from the official digitalocean/openapi spec DigitaloceanVpcCustomConfig, DigitaloceanVmCustomConfig (from @kykucloud/digitalocean)

Each alias is the provider’s real request type minus the fields Kyku’s portable contract already owns (name, cidr, instanceType, network/security-group identity, tags, SSH keys, and so on) — so it grows and shrinks with the underlying SDK/API automatically, with no hand-maintained field list to go stale.

Apply an alias with satisfies rather than a type annotation, so the object literal itself is still checked structurally:

customConfig: { EbsOptimized: true } satisfies AwsVmCustomConfig

Coverage is broadest for AWS (every resource manager has an alias) and GCP (every Compute-backed resource plus most of the rest, via SDK dependencies added purely for their request types — Identity/Role are the one documented gap, since no clean IAM Admin client library exists). Hetzner and DigitalOcean cover their most commonly customized resources (Vpc, Vm, SecurityGroup, LoadBalancer, plus SshKey for Hetzner and Database/DnsRecord/KubernetesCluster for DigitalOcean); a handful of resources on each (object storage, legacy DNS APIs, synthetic tag-based resources) don’t have — and structurally can’t usefully have — a generated alias, which is documented per-resource rather than silently missing. Untyped doesn’t mean unsupported: the merge/read/update passthrough exists for every resource on every provider regardless of whether a typed alias exists. Run kyku schema <resourceType> --provider <provider> to check current coverage for a specific pair rather than relying on this description staying up to date.

A key with no typed alias, or a key a generated alias doesn’t yet know the lifecycle of, is still accepted — Kyku warns and passes it through rather than hard-rejecting it, so a brand-new field the cloud API just shipped isn’t blocked by a stale catalog. If the key is a near-miss for a known one (a likely typo), the warning suggests the closest match (“did you mean ‘description’?”). Run kyku validate to see these warnings — they’re informational, not errors, so they don’t fail validation. The one thing that is a hard error is a customConfig key that collides with a portable field Kyku already manages (for example, setting AWS’s CidrBlock when you also set cidr — Kyku doesn’t know which one should win, so it refuses to guess).

Most customConfig keys are create-only: change one after the resource exists, and the next kyku plan shows a replace (destroy + recreate), because most cloud request fields aren’t safely re-postable in place. A small, explicitly curated set of keys per provider/resource is marked lifecycle-aware and plans an in-place update instead once the provider manager implements the corresponding update/action API call:

  • Hetzner Vpc: expose_routes_to_vswitch
  • DigitalOcean Vpc: description, default
  • GCP Vpc: description, mtu, routingConfig

Everything else defaults to create-only until a manager grows the matching update path. Kubernetes is the one exception to the “unlisted = create-only” default: every typed Kubernetes resource applies via server-side apply (an idempotent PATCH), so there’s no field that genuinely requires tearing the object down — customConfig changes on Kubernetes resources always plan as an update.

Run kyku schema <resourceType> --provider <provider> to see exactly which keys are lifecycle-aware for a given resource/provider pair right now, rather than relying on this list staying current — see the schema CLI reference.

A lifecycle-aware key means Kyku can apply the change in place — it doesn’t mean the underlying cloud operation is itself reversible. A few routed keys are one-way on the provider’s side; Kyku lets you apply them but doesn’t pretend the cloud will let you undo them:

  • DigitalOcean Vm ipv6: DigitalOcean supports enabling IPv6 on a running Droplet but not disabling it again. Setting ipv6: false after it’s already true throws rather than silently no-op’ing or attempting an API call DigitalOcean would reject anyway.
  • DigitalOcean Vm resize.disk: growing the disk via a resize action is permanent — a Droplet’s disk can never be shrunk again afterward. The resize also requires the Droplet to be powered off first; Kyku does not power-cycle it for you, so DigitalOcean rejects the action if the Droplet is running when the plan applies.
  • AWS Vpc InstanceTenancy: changing 'dedicated' to 'default' only affects instances launched after the change — existing dedicated instances stay dedicated. Left create-only rather than modeled as a true in-place toggle, since “in place” would be misleading here.

Some EC2 instance attributes can only change while the instance is stopped (InstanceType, enhanced-networking flags, kernel/ramdisk, and similar). Kyku’s AWS Vm routing deliberately does not implement an automatic stop → modify → restart cycle for these — doing so would mean silently taking a running instance offline mid-plan, which is a materially different (and riskier) operation than every other lifecycle-aware key documented here, all of which apply without any downtime. Attributes in this category are left create-only (a replace, not an update) rather than wrapped in an implicit reboot; if you need one of them changed in place, stop the instance yourself first. Only attributes AWS supports changing on a running instance are routed to update() — see packages/aws/src/resources/vm.ts for the current list.

Kubernetes resources accept customConfig too. It merges into the same target the pre-existing raw spec field already used on K8sDeployment/K8sStatefulSet/K8sIngress — applied after spec, so customConfig wins on any overlapping key. Resources without a spec of their own (K8sConfigMap, K8sSecret) merge customConfig into the whole object instead, with identity fields and anything already owned by a first-class field (data, stringData, secretType, …) protected from being overwritten.

K8sManifest and K8sHelmRelease don’t accept customConfig — the raw manifest or Helm values you hand them are already the full provider-native escape hatch, so there’s no separate merge target that would mean anything.

customConfig values are stored in Kyku’s state file like any other config — including in plaintext if you put a secret directly in it. Use an Kyku Secret reference (or your provider’s native secret-reference mechanism) for anything sensitive rather than inlining it into customConfig.