Multi-Site Deployment
This guide covers the patterns for deploying Stoker across multiple sites, environments, or regions — where each gateway needs unique configuration while sharing most of the repository structure.
The problem: unique systemName
Every Ignition gateway requires a unique systemName in config/resources/local/ignition/system-properties/config.json. In a multi-gateway deployment, this is the primary blocker for a shared git repository: the file is identical except for the system name field.
Stoker solves this in two ways depending on whether you prefer to modify source files or not:
- Content templating (
template: true) — author{{.GatewayName}}directly in the JSON source file. See the Content Templating guide. - JSON patches (
patches) — keep source files unmodified in git; the agent setssystemNameat sync time using a dot-notation path. See the JSON Patches guide.
Patches approach (source file unchanged)
mappings:
- source: "config/resources/ignition/core"
destination: "config/resources/ignition/core"
patches:
- file: "system-properties/config.json"
set:
systemName: "{{ .GatewayName }}"
The source file in git stays as {"systemName": "placeholder", ...} and the agent injects the correct value per gateway at sync time.
Repository structure
A well-organized multi-site repository separates shared config from per-gateway overrides:
config/
├── shared/ # Shared across all gateways
│ └── resources/
│ └── core/ # Ignition core config
├── system-properties/ # One file, templated at sync time
│ └── config.json # {"systemName": "{{.GatewayName}}"}
└── overlays/ # Deployment-mode specific
├── frontend/ # Frontend gateway config
└── backend/ # Backend gateway config
projects/
└── ... # Ignition project files
One GatewaySync CR per namespace
Each namespace (site, environment) gets its own GatewaySync CR. All gateways in the namespace share the same CR and use profiles to select their configuration.
production/
GatewaySync: site-sync
→ profile: site (for site-level gateways)
→ profile: area (for area-level gateways)
Example: single site with two profiles
apiVersion: stoker.io/v1alpha1
kind: GatewaySync
metadata:
name: site1-sync
namespace: site1
spec:
git:
repo: "https://github.com/org/ignition-config.git"
ref: "main"
auth:
githubApp:
appId: 123456
installationId: 789012
privateKeySecretRef:
name: github-app-key
key: privateKey
gateway:
port: 8088
api:
secretName: gw-api-key
sync:
defaults:
vars:
environment: "production"
excludePatterns:
- "**/.git/"
- "**/.gitkeep"
- "**/.resources/**"
- "**/.uuid" # IMPORTANT: never sync .uuid — it's gateway identity
profiles:
site:
mappings:
- source: "config/shared"
destination: "config/resources/core"
type: dir
- source: "config/system-properties"
destination: "config/resources/local/ignition/system-properties"
type: dir
template: true # each gateway gets its own systemName
- source: "projects"
destination: "projects"
type: dir
required: true
area:
mappings:
- source: "config/shared"
destination: "config/resources/core"
type: dir
- source: "config/system-properties"
destination: "config/resources/local/ignition/system-properties"
type: dir
template: true
- source: "projects/area"
destination: "projects"
type: dir
required: true
Gateways annotate themselves with the profile they use:
# On the Ignition gateway pod (in its Helm values or deployment spec)
annotations:
stoker.io/inject: "true"
stoker.io/profile: "area" # select the area profile
stoker.io/gateway-name: "area1" # override auto-detected name
Example: multi-environment with vars
For deployments where the same profile must behave differently per environment, use vars in defaults:
spec:
sync:
defaults:
vars:
environment: "production"
historyProvider: "sql-historian"
profiles:
default:
mappings:
- source: "config/base"
destination: "config/resources/core"
type: dir
template: true
The base config can reference these values:
{
"provider": "{{.Vars.historyProvider}}",
"environment": "{{.Vars.environment}}"
}
Different namespaces (dev, staging, production) each have their own GatewaySync CR with different defaults.vars, while sharing the same repository structure.
Example: StatefulSet with multiple replicas
For StatefulSets where each replica manages a different set of resources, use {{.PodName}} to give each replica a unique system name:
spec:
sync:
profiles:
default:
mappings:
- source: "config/system-properties"
destination: "config/resources/local/ignition/system-properties"
type: dir
template: true # resolves {{.PodName}} to "ignition-0", "ignition-1", etc.
- source: "projects"
destination: "projects"
type: dir
In the template file:
{
"systemName": "{{.PodName}}",
"httpPort": 8088
}
Each pod (ignition-0, ignition-1) gets a distinct system name without any pod-specific config in git.
Promoting across environments with Kargo
Stoker integrates naturally with Kargo + ArgoCD for environment promotion. Kargo promotes a git commit by updating spec.git.ref on the GatewaySync CR, or by triggering the webhook receiver with a Kargo notification.
Typical flow:
- Kargo promotes commit
abc123to theproductionstage - Kargo sends a
POST /webhook/{namespace}/{crName}with{"ref": "abc123"} - Stoker resolves the ref and syncs all gateways to that commit
- Each gateway renders its templates from the commit's file contents
Namespacing for multi-site
A common pattern for multi-site Kubernetes deployments:
cluster/
├── site1/ # namespace
│ └── GatewaySync: site1-sync
├── site2/ # namespace
│ └── GatewaySync: site2-sync
└── site3/
└── GatewaySync: site3-sync
Each site has its own namespace with:
- Its own GatewaySync CR (can track different refs per site)
- Its own GitHub App token Secret (created by the controller)
- Its own gateway API key Secret
- Namespace label
stoker.io/injection=enabled(only ifwebhook.namespaceSelector.requireLabel=true)
Important: protect the .uuid file
Ignition's .uuid file contains the gateway's unique identity. Syncing it would cause two gateways to share the same identity, breaking gateway network routing and historian data.
Always exclude it:
spec:
sync:
defaults:
excludePatterns:
- "**/.git/"
- "**/.gitkeep"
- "**/.resources/**"
- "**/.uuid" # required — never sync the uuid file
Checklist before deploying
-
**/.uuidis inexcludePatterns -
template: trueis set on any mapping that contains{{...}}in file contents -
patchesare used for targeted JSON field updates (no source-file modification required) -
varskeys in templates or patch values match the keys defined inspec.sync.defaults.varsorspec.sync.profiles.<name>.vars - Each gateway pod has
stoker.io/inject: "true"and optionallystoker.io/profile: "<name>" - If
webhook.namespaceSelector.requireLabel=true, the namespace has labelstoker.io/injection=enabled(not required with default configuration) - Binary files (images, compiled modules) are in a separate mapping without
template: true, or are excluded - Gateway API key Secret exists in the namespace
- Git auth Secret exists in the namespace (GitHub App PEM, SSH key, or token)