Args and Vars Merge Strategy
Args and Vars can be set at multiple levels and Nstance Server uses the same merge stragegy for both.
Merges work by replacing the higher priority layer over the lower priorities layer, except where both values are objects and then it merges the object fields and recurses down into each value of that object.
For example with Args:
defaults.args:
{
"a": 1,
"b": { "hello": "world", "always": "here" },
"c": { "goodbye": "world" }
}templates.example.args:
{
"a": "2",
"b": { "hello": "there", "location": "world" },
"c": 3
}merged output:
{
"a": "2",
"b": { "hello": "there", "always": "here", "location": "world" },
"c": 3
}This is a straightforward recursive Go function:
func deepMerge(dst, src map[string]any) {
for k, v := range src {
if vMap, ok := v.(map[string]any); ok {
if dMap, ok := dst[k].(map[string]any); ok {
deepMerge(dMap, vMap)
continue
}
}
dst[k] = v
}
}Image Resolution
Nstance supports automatic resolution of cloud provider images (e.g., latest Debian AMI) based on predefined criteria, eliminating the need to hardcode region-specific image IDs.
- Provider-specific filters to lookup latest AMIs/images
- Cached in SQLite, refreshed periodically (default 6 hours)
- Optional fallback image IDs if lookup fails
Provider Support
Currently this feature is only useful for AWS.
Different cloud providers handle image references differently:
- AWS (EC2): Uses region-specific AMI IDs that must be looked up. Nstance automatically queries EC2 DescribeImages API with filters and caching.
- GCP (Compute Engine): Uses image families (e.g.,
projects/debian-cloud/global/images/family/debian-13) that automatically resolve to latest. No lookup needed. - Oracle Cloud (OCI): Would benefit from lookup mechanism (similar to AWS), and may be added in the future.
Configuration
Images are configured in the images block with provider-specific filters:
{
"images": {
"debian_13_arm64": {
"provider": "aws",
"filters": [
{"name": "name", "values": ["debian-13-arm64-*"]},
{"name": "owner-id", "values": ["136693071363"]} // Debian
],
"sort": "creation-date",
"order": "desc",
"fallback": "ami-026fccd88446aa0bf" // Optional fallback if lookup fails
}
}
}Note that image name/map key should be a valid Go template identifiers (alphanumeric + underscores, starting with letter/underscore).
Configuration Fields:
provider: Cloud provider (aws) - must match server providerfilters: Array of provider-specific filters (e.g., AWS filter name/values)sort: Field to sort results by (creation-date,name)order: Sort order (asc,desc)fallback: Optional fallback image ID if lookup fails
Usage in Templates
Resolved images are available in userdata and args templates via .Image map:
{
"templates": {
"knc": {
"args": {
"ImageId": "{{ .Image.debian_13_arm64 }}" // Resolved AMI ID
}
}
}
}Alternative syntax using index helper for kebab-case names:
{{ index .Image "debian-13-arm64" }}Resolution Process
- On Shard Leader Election: Service starts and performs initial image resolution
- Lookup: Queries provider API (e.g., EC2 DescribeImages) with configured filters
- Sort & Select: Sorts results and selects first match (e.g., latest by creation date)
- Cache: Stores resolved images in SQLite (
imagestable) - Periodic Refresh: Re-resolves at configured interval (default 6 hours)
Error Handling & Fallback
If image resolution fails:
- Try Cache: Load previously resolved value from SQLite
- Use Fallback: Use configured
fallbackimage ID if specified - Fail Provisioning: If no cache and no fallback, instance provisioning fails with error log
Cache persists across server restarts and does not expire (persists until next successful lookup).
Args
Args are passed to the provider VM creation SDK function call. Args are provider-specific and support different fields for AWS (RunInstances) and GCP (Instance) configurations. For example on AWS, Args effectively becomes AWS RunInstances input.
Args are processed as a text/template after the merge strategy has applied, recursively interpolating string values in nested objects and arrays. Note that templating can only be used inside JSON string values, for example:
{
"someKey": "{{ .Var.SOME_VALUE }}"
}AWS Args
AWS Args map directly to RunInstances input fields. You cannot set InstanceType, SubnetId, UserData, MinCount, or MaxCount (these are managed by Nstance).
GCP Args
GCP Args are applied to the computepb.Instance resource. The following fields are supported:
| Arg | Type | Description |
|---|---|---|
SourceImage | string | Boot disk image (e.g., "projects/debian-cloud/global/images/family/debian-13") |
ServiceAccount | string | Service account email. Sets cloud-platform scope automatically. |
ServiceAccounts | array | Full service account config (alternative to ServiceAccount for custom scopes) |
Labels | object | Additional GCP labels merged with Nstance-managed labels |
NetworkTags | array | GCP network tags for firewall rule targeting (e.g., ["nstance-agent-us-central1-a"]) |
Preemptible | bool | Use preemptible VM scheduling |
ProvisioningModel | string | VM provisioning model (e.g., "SPOT") |
OnHostMaintenance | string | Host maintenance behavior (e.g., "TERMINATE") |
AutomaticRestart | bool | Automatically restart on failure |
MinCpuPlatform | string | Minimum CPU platform (e.g., "Intel Cascade Lake") |
GuestAccelerators | array | GPU accelerator configs |
ShieldedInstanceConfig | object | Shielded VM settings |
ConfidentialInstanceConfig | object | Confidential VM settings |
ResourcePolicies | array | Resource policy URLs |
Example:
{
"defaults": {
"args": {
"SourceImage": "projects/debian-cloud/global/images/family/debian-13",
"ServiceAccount": "[email protected]"
}
},
"groups": {
"default": {
"workers": {
"template": "default",
"size": 3,
"args": {
"NetworkTags": ["nstance-agent-us-central1-a"],
"Labels": {
"nstance-group": "workers"
}
}
}
}
}
}Note: Nstance automatically manages core GCP labels (nstance-managed, nstance-instance-id, nstance-cluster-id, nstance-shard, nstance-group, nstance-template, nstance-instance-kind) and uses these labels (not network tags) for instance identification and filtering.
Vars
Vars are set in a hierarchy, with order of precedence:
- (MachinePool): Group -> Template -> Global
- (Machine): Group -> Template -> Global
For example:
{
"vars": {
"EXAMPLE": "one",
},
"templates": {
"default": {
"vars": {
"EXAMPLE": "two",
},
},
},
"groups": {
"blue": {},
"green": {
"template": "default",
},
"red": {
"template": "default",
"vars": {
"EXAMPLE": "three",
},
},
"black": {
"vars": {
"EXAMPLE": "four",
},
},
},
}Given the above example config, the .Var.EXAMPLE value in userdata:
- for group
blueisone. - for group
greenistwo. - for group
redisthree. - for group
blackisfour.
Userdata Templates
The defaults and templates block can specify a userdata configuration object:
{
"templates": {
"default": {
"userdata": {
"source": "inline", // "inline" (default) or "url"
"encoding": "plain", // "plain" (default), "base64", "gzip", "base64+gzip"
"content": "#!/bin/bash\necho 'Hello {{ .Instance.ID }}'" // or the URL
}
}
}
}Fields:
source: Where the content comes from"inline"(default): Thecontentfield contains the userdata directly"url": Thecontentfield contains a URL to fetch the userdata from
encoding: How the content is encoded"plain"(default): Plain text with escaped newlines"base64": Base64 encoded content"gzip": Gzip compressed content (only valid withsource: "url")"base64+gzip": Base64 encoded, gzip compressed content
content: The userdata content or URL (required)
The default userdata template is only used if no userdata template is specified for the template.
The userdata template is processed as a Go text/template, where the template can reference:
Merged
Varse.g.{{ .Vars.MY_VAR }}(see Args & Vars Merge Strategy)Server information such as:
.Server.Sharde.g.us-west-2a-1.Server.RegistrationAddri.e. the IP and port for agent registration (e.g.10.0.0.1:8992).Server.AgentAddri.e. the IP and port for agent gRPC service (e.g.10.0.0.1:8994)
Provider information such as:
.Provider.Kinde.g.aws.Provider.Regione.g.us-west-2.Provider.Zonee.g.us-west-2a
Instance information (derived from Pool/MachinePool or Instance/Machine configuration, which can be derived from the instance template) such as:
.Instance.Arche.g.arm64.Instance.Typee.g.t4g.medium.
Templated Files
In addition to certificates and secrets, the Nstance Server can generate and send templated files to agents. These files are dynamically generated using Go text/template with access to instance, cluster, and configuration data.
Template Types
Templated files support three distinct template formats:
kind: env
For environment variable files (.env format), use kind: "env" with a key-value template object:
"instance.env": {
"kind": "env",
"template": {
"INSTANCE_ID": "{{ .Instance.ID }}",
"ENVIRONMENT": "{{ .Vars.Environment }}",
"K8S_NODE_LABELS": "{{ .Vars.KUBELET_NODE_LABELS }}",
"CLUSTER_FQDN": "{{ .Cluster.FQDN }}"
}
}This generates standard environment file format:
INSTANCE_ID=knc0000000001r010000000000000
ENVIRONMENT=production
K8S_NODE_LABELS=controlplane
CLUSTER_FQDN=example-cluster.cluster.coolkind: json
For JSON configuration files, use kind: "json" with a template object where string values can contain templates:
"kubelet-config.json": {
"kind": "json",
"template": {
"kind": "KubeletConfiguration",
"apiVersion": "kubelet.config.k8s.io/v1beta1",
"address": "{{ .Instance.IP4 }}",
"port": 10250,
"clusterDomain": "cluster.local",
"nodeLabels": {
"instance.example.com/id": "{{ .Instance.ID }}",
}
}
}String values are processed as templates while preserving JSON structure, numbers, booleans, and arrays.
This generates a JSON document file format e.g.:
{
"kind": "KubeletConfiguration",
"apiVersion": "kubelet.config.k8s.io/v1beta1",
"address": "172.18.0.1",
"port": 10250,
"clusterDomain": "cluster.local",
"nodeLabels": {
"instance.example.com/id": "knc0000000001r010000000000000",
}
}kind: string
For custom file formats or when you need full control over the output format:
"custom-script.sh": {
"kind": "string",
"template": "#!/bin/bash\necho \"Instance {{ .Instance.ID }} starting\"\nexport ENV={{ .Vars.Environment }}"
}e.g.
#!/bin/bash
echo "Instance knc0000000001r010000000000000 starting"
export ENV=productionTemplate Data
All template types have access to the same template data:
- Instance:
{{ .Instance.ID }},{{ .Instance.Type }},{{ .Instance.Hostname }},{{ .Instance.IP4 }},{{ .Instance.IP6 }} - Server:
{{ .Server.Shard }},{{ .Server.RegistrationAddr }},{{ .Server.AgentAddr }} - Provider:
{{ .Provider.Kind }},{{ .Provider.Region }},{{ .Provider.Zone }} - Cluster:
{{ .Cluster.Slug }},{{ .Cluster.FQDN }} - Variables:
{{ .Vars.ENVIRONMENT }}- All merged variables from global → template → group hierarchy - Images:
{{ .Image.debian_13_arm64 }}- Resolved image IDs from Image Resolution configuration
Template data uses the same variable merging strategy as userdata templates.