Server Configuration
Nstance Server reads a JSONC configuration file from object storage at shard/{shard}/config.jsonc.
The top-level shape is:
cluster- cluster-scoped identity, storage, secrets, and cluster leader election settings.shard- shard-scoped server runtime, infrastructure provider, subnet, and shard leader election settings.load_balancers- load balancers that groups can register instances with.images- optional image lookup configuration.certificates- certificate templates for files generated by the server.defaults- global args, vars, and userdata inherited by templates/groups.templates- instance templates referenced by groups and on-demand instances.groups- tenant-scoped static group configuration.nat- optional tenant-scoped Podplane Managed NAT configuration.
Below is an example/reference configuration file for Nstance Server, using example data for a fictional AWS-backed Kubernetes cluster:
{
"cluster": {
"id": "example-cluster", // Cluster ID (lowercase alphanumeric + hyphens)
// Optional: separate storage for cluster-scoped data. If omitted, the shard bucket is used with the "cluster/" prefix.
"storage": {
"provider": "s3",
"bucket": "example-nstance-state",
"region": "us-west-2",
"prefix": "cluster/"
},
"secrets": {
// AWS default. Names are appended below this Parameter Store path.
"provider": "aws-parameter-store",
"prefix": "/nstance/",
// Optional: secret cache TTL. Omit or set to 0 to disable caching.
"cache_ttl": "5m"
},
// Optional: cluster-wide leader election timings. Omit to use server defaults.
"leader_election": {
"enabled": true,
"frequent_interval": "5s",
"infrequent_interval": "30s",
"leader_timeout": "15s"
}
},
"shard": {
"id": "us-west-2a", // Shard ID (lowercase alphanumeric + hyphens)
"infra": {
"provider": "aws",
"region": "us-west-2",
"zone": "us-west-2a", // aka CAPI "Failure Domain"
"options": {
"profile": "production"
}
},
"bind": {
"health_addr": "0.0.0.0:8990", // HTTP health endpoint for ASG/LB health checks (503 until config is loaded, then 200)
"election_addr": "0.0.0.0:8991", // HTTPS leader election health endpoint for peer-to-peer leader election health checks
"registration_addr": "0.0.0.0:8992", // Agent/Operator registration (anonymous)
"operator_addr": "0.0.0.0:8993", // Operator connections (authenticated)
"agent_addr": "0.0.0.0:8994" // Agent connections (authenticated)
},
"advertise": {
"health_addr": "10.0.0.1:8990", // HTTP health endpoint advertised for ASG/LB health checks and monitoring
"election_addr": "10.0.0.1:8991", // HTTPS leader election health endpoint advertised to peer servers
"registration_addr": "10.0.0.1:8992", // Address used by Nstance Agents and Nstance Operator to register
"operator_addr": "10.0.0.1:8993", // Address used by Nstance Operator to connect
"agent_addr": "10.0.0.1:8994" // Address used by Nstance Agents to connect
// If the host part is empty, "0.0.0.0", or "::", it is auto-detected from the primary network interface.
},
// Optional: stable leader network configuration for shard leadership.
"leader_network": {
// AWS requires both ip and interface_id (ENI ID). Google Cloud uses ip and leaves interface_id empty.
"ip": "10.0.0.100", // Stable leader IP address (ENI private IP for AWS, reserved IP for Google Cloud)
"interface_id": "eni-0abc123def456789" // AWS ENI ID (required for AWS, not used for Google Cloud)
},
"subnet_pools": {
"nat": ["subnet-00000000"], // Reserved small-cluster NAT node subnet
"control-plane": ["subnet-12345678"], // Maps subnet pools to provider subnet IDs
"ingress": ["subnet-23456789"], // Subnets for ingress/load balancer nodes
"workers": ["subnet-87654321", "subnet-abcdef"] // Each key can map to multiple subnets for capacity
},
// Optional: restrict dynamic groups to these subnet pool IDs. Omit or set empty to allow any configured subnet pool.
"dynamic_subnet_pools": ["workers"],
// Optional: omit shard timing fields to use server defaults.
"request_timeout": "30s",
"create_rate_limit": "1s",
"health_check_interval": "60s",
"default_drain_timeout": "5m",
"image_refresh_interval": "6h", // How often to refresh image resolutions
"shutdown_timeout": "10s",
// Optional: omit garbage_collection to use server defaults.
"garbage_collection": {
"interval": "2m", // How often to backfill provider instances and look for dangling ones
"registration_timeout": "5m", // How long to wait for instance registration before terminating as dangling
"deleted_record_retention": "30m" // How long to keep deleted instance records in the local database
},
// Optional: omit expiry to disable age-based expiry.
"expiry": {
"eligible_age": "504h", // 21 days
"forced_age": "720h", // 30 days
"ondemand_age": "168h" // 7 days
},
// Optional: shard leader election timings. Omit to use server defaults.
"leader_election": {
"enabled": true,
"frequent_interval": "5s",
"infrequent_interval": "30s",
"leader_timeout": "15s"
},
// Optional: jittered delay range before exiting after fatal errors. Omit to use server defaults.
"error_exit_jitter": {
"min_delay": "10s",
"max_delay": "40s"
}
},
// Optional: logical backend registration targets that groups can reference by key.
// Entries use provider-specific AWS target group, Google Cloud NEG, or tunnel listener shapes.
"load_balancers": {
"www": {
"provider": "aws",
"target_groups": [{
"arn": "arn:aws:elasticloadbalancing:us-west-2:123412341234:targetgroup/example-www/abcdef1234567890",
"listener_port": 443,
"target_port": 443,
"proxy_port": 8443
}]
}
},
// Optional: image lookup configuration. Omit if templates use fixed provider image IDs directly.
"images": {
"debian_13_arm64": {
"provider": "aws",
"filters": [
{"name": "name", "values": ["debian-13-arm64-*"]},
{"name": "virtualization-type", "values": ["hvm"]},
{"name": "architecture", "values": ["arm64"]}
],
"owners": ["136693071363"], // Debian
"sort": "creation-date",
"order": "desc",
// Optional: fallback image ID if lookup fails and no cached image is available.
"fallback": "ami-026fccd88446aa0bf"
},
"debian_13_amd64": {
"provider": "aws",
"filters": [
{"name": "name", "values": ["debian-13-amd64-*"]},
{"name": "virtualization-type", "values": ["hvm"]},
{"name": "architecture", "values": ["x86_64"]}
],
"owners": ["136693071363"],
"sort": "creation-date",
"order": "desc",
// Optional: fallback image ID if lookup fails and no cached image is available.
"fallback": "ami-0abcd1234efgh5678"
}
},
// Optional: certificate templates for generated files.
"certificates": {
"kubelet.client": {
"kind": "client",
"cn": "system:node:{{ .Instance.ID }}",
"organization": ["system:nodes"],
"dns": [
"localhost",
"{{ .Instance.ID }}",
"{{ .Instance.Hostname }}"
],
"ip": [
"127.0.0.1",
"::1",
"{{ .Instance.IP4 }}",
"{{ .Instance.IP6 }}"
],
// Optional: certificate TTL in hours. Omit to use server default.
"ttl": 8760
},
"kubelet.server": {
"kind": "server",
"cn": "kubelet",
"dns": [
"localhost",
"{{ .Instance.ID }}",
"{{ .Instance.Hostname }}"
],
"ip": [
"127.0.0.1",
"::1",
"{{ .Instance.IP4 }}",
"{{ .Instance.IP6 }}"
]
},
"kube-apiserver.server": {
"kind": "server",
"cn": "kubernetes",
"dns": [
"localhost",
"kubernetes",
"kubernetes.default",
"kubernetes.default.svc",
"kubernetes.default.svc.cluster",
"kubernetes.default.svc.cluster.local",
"kubernetes.svc.cluster.local",
"{{ .Instance.ID }}",
"{{ .Instance.Hostname }}",
"{{ .Vars.ClusterFQDN }}"
],
"ip": [
"127.0.0.1",
"::1",
"198.18.0.1",
"fdc6::1",
"{{ .Instance.IP4 }}",
"{{ .Instance.IP6 }}"
]
}
},
"defaults": {
"args": {
// Passed to the provider instance creation API. For AWS, this maps to RunInstances.
// Nstance manages instance type, subnet, userdata, and count fields.
"Ipv6AddressCount": 1,
"BlockDeviceMappings": [
{
"DeviceName": "/dev/sda1", // root volume
"Ebs": {
"VolumeSize": 50, // in GB
"VolumeType": "gp3",
"Encrypted": true
}
}
],
"MetadataOptions": {
"HttpEndpoint": "enabled", // default
"HttpProtocolIpv6": "disabled", // default
"HttpPutResponseHopLimit": 1, // default
"HttpTokens": "required", // always require IMDSv2
"InstanceMetadataTags": "enabled"
},
"PrivateDnsNameOptions": {
"EnableResourceNameDnsARecord": true
},
"TagSpecifications": [
{
"ResourceType": "instance",
"Tags": [
{"Key": "InstanceId", "Value": "{{ .Instance.ID }}"},
{"Key": "InstanceKind", "Value": "{{ .Instance.Kind }}"}
]
}
]
},
"vars": {
"ClusterSlug": "example-cluster",
"ClusterFQDN": "example-cluster.cluster.cool",
"Environment": "production",
"CloudBillingID": "123412341234",
"SSHAuthorizedKey": "",
"TelemetryBucket": ""
},
// Optional: global userdata used by templates that do not define userdata.
"userdata": {
"content": "#!/bin/bash\necho \"default userdata for {{ .Instance.ID }}\""
}
},
"templates": {
"nat": {
"kind": "nat",
"arch": "arm64",
"args": {
"ImageId": "{{ .Image.debian_13_arm64 }}", // resolved from images config
"SecurityGroupIds": ["sg-12341234abcd1234ab"],
"IamInstanceProfile": {
"Arn": "arn:aws:iam::123412341234:role/example-instance-role"
}
},
"userdata": {
"content": "#!/bin/bash\necho \"hello from {{ .Instance.ID }}\""
},
// template defaults:
"size": 1,
"instance_type": "t4g.small",
"subnet_pool": "control-plane",
"vars": {}
},
"knc": {
"kind": "knc", // 3 lowercase letters, used as prefix for instance ID (puidv7)
"arch": "arm64", // used to validate instance type
"files": {
// send secret `tunnel-20250924.json` as `tunnel.json` file
"tunnel.json": {
"kind": "secret",
"source": "tunnel-20250924.json"
},
// send a file from object storage
"bootstrap-config.json": {
"kind": "storage",
"source": "files/{{ .Vars.Environment }}/bootstrap-config.json"
},
// send a certificate generated using a public key sent from the agent
"kubelet.client.crt": {
"kind": "certificate",
"template": "kubelet.client",
"key": {
"source": "agent",
"name": "kubelet.client.pub"
}
},
// send templated environment file
"instance.env": {
"kind": "env",
"template": {
"INSTANCE_ID": "{{ .Instance.ID }}",
"ENVIRONMENT": "{{ .Vars.Environment }}",
"K8S_NODE_LABELS": "{{ .Vars.KUBELET_NODE_LABELS }}",
"CLUSTER_FQDN": "{{ .Vars.ClusterFQDN }}"
}
},
// send templated JSON configuration file
"kubelet-config.json": {
"kind": "json",
"template": {
"kind": "KubeletConfiguration",
"apiVersion": "kubelet.config.k8s.io/v1beta1",
"address": "{{ .Instance.IP4 }}",
"port": 10250,
"cgroupDriver": "systemd",
"clusterDomain": "cluster.local",
"nodeLabels": {
"instance.example.com/id": "{{ .Instance.ID }}"
}
}
},
"custom.conf": {
"kind": "string",
"template": "instance={{ .Instance.ID }}\ncluster={{ .Vars.ClusterSlug }}\n"
}
},
"args": {
"ImageId": "{{ .Image.debian_13_arm64 }}", // resolved from images config
"SecurityGroupIds": ["sg-23412341abcd1234ab"],
"IamInstanceProfile": {
"Arn": "arn:aws:iam::123412341234:role/example-instance-role"
},
// Example of overriding a default arg. Objects are merged recursively; non-object values replace the lower-priority value.
"BlockDeviceMappings": [
{
"Ebs": {
"VolumeSize": 100 // in GB
}
}
]
},
"userdata": {
"source": "url",
"content": "https://example.com/userdata/knc_arm64.sh"
},
// template defaults:
"size": 1,
"instance_type": "t4g.medium",
"subnet_pool": "control-plane",
"vars": {
"K8SAPIHostname": "example-cluster.cluster.cool",
"OIDCIssuer": "https://auth.example.com",
"RegistryBucket": ""
}
},
"knd": {
"kind": "knd",
"arch": "arm64",
"args": {
"ImageId": "{{ .Image.debian_13_arm64 }}"
},
"instance_type": "t4g.large",
"subnet_pool": "workers"
}
},
"groups": {
"default": {
"nat": {
"template": "nat",
"instance_type": "t4g.small",
"subnet_pool": "control-plane" // Subnet pool ID (resolved via shard.subnet_pools)
},
"main": {
"template": "knc",
"size": 1,
"subnet_pool": "control-plane",
"vars": {
"KUBELET_NODE_LABELS": "controlplane"
}
},
"ingress": {
"template": "knd",
"subnet_pool": "ingress",
"load_balancers": ["www"],
"vars": {
"KUBELET_NODE_LABELS": "traefik"
}
},
"apps": {
"template": "knd",
"size": 2,
"instance_type": "t4g.xlarge",
"subnet_pool": "workers",
"drain_timeout": "10m",
"vars": {}
}
}
},
"nat": {
"default": {
"group": "nat",
"network_identity_pool": "default-nat-identities",
"last_node_grace_period": "10m",
"instance_type_ladder": ["t4g.small", "t4g.medium", "t4g.large"],
"scale_up_thresholds": {
"throughput_percent": 80,
"packets_per_second_percent": 80,
"conntrack_percent": 80,
"cpu_percent": 80,
"packet_drops_per_second": 1
},
"scale_down_thresholds": {
"throughput_percent": 30,
"packets_per_second_percent": 30,
"conntrack_percent": 30,
"cpu_percent": 30,
"packet_drops_per_second": 0
},
"small_cluster": {
"initial_subnet": "nat",
"max_instances": 10
}
}
}
}AWS Secrets Manager (aws-secrets-manager) and encrypted object storage (object-storage) are explicit alternatives. For object storage, encryption_key.provider and entries in old_encryption_keys may use aws-parameter-store; set source to a SecureString parameter name such as /nstance/encryption-key. Parameter Store stores raw UTF-8 text (not base64) and standard-tier values are limited to 4 KiB.
cluster
The cluster block defines cluster-scoped configuration shared by all shards:
id- cluster identifier.storage- optional separate cluster-scoped storage. If omitted, the shard bucket is used with thecluster/prefix.secrets- secret store configuration and encryption key settings.leader_election- optional cluster leader election timing settings.
shard
The shard block defines the Nstance Server process and shard-local infrastructure behavior:
id- unique shard identifier.infra- infrastructure provider (aws,google,mock,tmux, orproxmox), region, zone, and provider-specific options.bind- addresses the server listens on.advertise- addresses clients should connect to.leader_network- stable shard leader IP configuration.subnet_pools- maps subnet pool IDs to provider subnet IDs.dynamic_subnet_pools- optional list limiting which subnet pools dynamic groups can use.request_timeout- provider request timeout.create_rate_limit- minimum delay between provider create calls.health_check_interval- agent health check interval.default_drain_timeout- default graceful drain timeout for group scale-down/replacement.image_refresh_interval- image lookup refresh interval.shutdown_timeout- graceful server shutdown timeout.garbage_collection- provider backfill and dangling instance cleanup settings.expiry- optional instance age limits for automatic rotation.leader_election- optional shard leader election timing settings.error_exit_jitter- optional delay range before exiting after fatal startup/runtime errors.
load_balancers
Load balancers are defined at the top level and referenced by group load_balancers lists. Each entry is a logical backend registration target, not necessarily a distinct physical load balancer. For example, separate ingress and controlplane entries may point at different listener backends on the same provider load balancer, or at different load balancers.
AWS load balancers use:
{
"provider": "aws",
"target_groups": [{"arn": "arn:aws:elasticloadbalancing:...", "listener_port": 443, "target_port": 443, "proxy_port": 8443}]
}Google Cloud load balancers use:
{
"provider": "google",
"network_endpoint_groups": ["example-us-central1-a"],
"frontends": [{"ip": "34.10.20.30", "port": 443}]
}Tunnel load balancers use listeners entries containing target_port and proxy_port. Tunnel entries do not invoke infrastructure provider membership APIs.
certificates and template files
certificates defines reusable TLS certificate templates. Templates can be referenced by template files entries with kind: "certificate".
Template files support these kinds:
certificate- generate a certificate from a named certificate template. Thekeymust explicitly name a public key sent from the agent.secret- fetch a secret bysourcefrom the configured secrets store.storage- fetch an object bysourcefrom object storage. Thesourcesupports Go template variables.env- render a.envfile from a string-valued template object.json- render a JSON file from a template object.string- render a raw string template for custom formats.
defaults, templates, and groups
defaults, template config, and group config are merged to produce the final instance configuration.
defaults.args,templates.<template>.args, andgroups.<tenant>.<group>.argsare merged with later layers overriding earlier layers.defaults.vars,templates.<template>.vars, andgroups.<tenant>.<group>.varsare merged the same way.defaults.userdatais used when a template does not specify userdata.templatesdefine reusable instance configuration.groupsare nested by tenant:groups.<tenant>.<group>.
For more information see: Image Resolution, Args & Vars Merge Strategy, Args, Vars, and Userdata Templates.
nat
nat.<tenant> enables Podplane Managed NAT for that tenant. It must contain
group, small_cluster, or both. group is a singular reference to a group in
the same tenant, so at most one dedicated NAT group can be configured. A
dedicated group must omit size and set its starting instance_type. The
tenant NAT configuration contains instance_type_ladder, ordered from lowest
to highest capacity, scale-up and scale-down thresholds, scale_up_window (2m-5m),
scale_down_window (20m-30m), cooldown (at least 10m), and
replacement_timeout. Window defaults are 3m, 25m, 10m, and 10m respectively.
Utilization thresholds default to 80% up and 30% down; packet-drop thresholds
default to 1 and 0 drops/second.
network_identity_pool names the pre-provisioned stable NAT network identities,
such as AWS ENIs and addresses or GCP reserved addresses, and is required with a
dedicated group. last_node_grace_period defaults to 10m.
small_cluster.initial_subnet references a subnet-pool key and
small_cluster.max_instances must be positive. The initial subnet is exclusively
reserved for that tenant: it cannot appear in templates, groups, the dynamic
subnet allowlist, or another tenant’s NAT entry. Because an empty dynamic subnet
allowlist permits every pool, small-cluster NAT requires an explicit allowlist
that excludes its initial subnet.