Settings
To help with customizing OpsChain to your needs, a number of settings can be configured at the global, project, environment, asset and change levels, each overriding the previous one. When a default value is mentioned, it usually refers to the value automatically assigned to the global settings, which serve as a fallback and can be configured via the system configuration page in the GUI or at installation time, as described in the configuration guide. Settings are stored in JSON format and the ones deemed sensitive (e.g. password, tokens) are automatically encrypted when saved.
Changes to these settings apply live across all clusters in a high availability deployment — there is no need to restart the OpsChain API for an updated setting to take effect.
The settings below can be configured at any level, unless otherwise specified.
Agent settings
agent.disable_host_alias
Default value: false
OpsChain configures a hostAlias for the API hostname in the pod for accessing the OpsChain API ingress from within the pod.
Setting this to true means this host alias will not be created. This may be necessary if you have a DNS entry for the API hostname that you want used because it needs to point to a different ingress than the OpsChain Kubernetes ingress.
agent.node_selector
Default value: (no selector)
Restricts the Kubernetes nodes an agent pod may be scheduled onto. The value is a JSON object of node labels, supplied as a string:
{
"agent": {
"node_selector": "{\"kubernetes.io/os\":\"linux\"}"
}
}
When this setting is left unset, agent pods use runner.node_selector instead. Set it to place agents on different nodes to the pods that run your changes - agents are long lived, whereas a change's pods come and go.
The setting is read when the agent pod is created, so it can be configured at any level and a change takes effect the next time the agent starts. A value OpsChain cannot read as a JSON object is rejected when it is saved; where one was stored before that validation existed, the agent reports a configuration error rather than starting.
agent.script_path
Default value: /opt/opschain/agent.sh
The path to the script that will be launched upon starting an agent.
Agent image settings
These settings modify the base FROM agent image, used to build agents. The agent image string is constructed in the following way:
FROM {repository}/{name}:{image_tag}
agent.image_override
Default value: not configured (there is no image override)
This setting overrides the whole image string used in the FROM directive and hence the name, repository, and image_tag settings will be ignored. This allows you to provide an alternative image registry, for example: https://customer-registry.example.com/customer/agent-image:version or ghcr.io/customer/agent-image:version.
The opschain-image-secret secret in the Kubernetes cluster must have credentials for the registry to allow the OpsChain build service to pull the images.
agent.image_tag
Default: Current version, e.g. 2025-01-01.
The tag of the agent image.
agent.name
Default: opschain-runner-enterprise
The name of the agent image.
agent.repository
Default: limepoint
The repository where the agent image is stored. Refers to a Docker registry or a container image repository.
API worker autoscaling settings
Each opschain-api pod runs a fixed number of worker processes to handle incoming API requests. During bursts of concurrent traffic, all worker processes can end up busy at once, causing further requests to queue rather than being handled immediately.
These settings let OpsChain automatically grow or shrink the number of worker processes in each pod in response to this queuing, within a configured minimum and maximum, instead of relying solely on a fixed count. Every autoscaling decision is recorded as an event, whether or not the worker count is actually changed.
api_autoscaler.mode
Default value: active
Accepted values: disabled, dry_run, active
Scope: global
Controls whether autoscaling is allowed to change the worker count:
disabled— autoscaling is turned off.dry_run— OpsChain evaluates whether it would grow or shrink the worker count and records the decision as an event, but never changes the worker count. Useful for observing autoscaling behaviour before relying on it.active— OpsChain grows or shrinks the worker count for real, as well as recording the same events. This is the default.
api_autoscaler.min_workers
Default value: 3
Scope: global
The minimum number of worker processes autoscaling is allowed to shrink a pod down to.
api_autoscaler.max_workers
Default value: 10
Scope: global
The maximum number of worker processes autoscaling is allowed to grow a pod up to.
Choose a value your pod's memory limits can safely support at full worker count. OpsChain checks available memory headroom before growing further, but this check depends on the cluster's cgroup configuration and may not be available on every Kubernetes distribution — do not rely on it as the only safeguard against over-provisioning.
Each worker process opens its own database connection pool, so a higher max_workers also means more total database connections — this is not checked automatically. Make sure your database's max_connections has enough headroom for max_workers at full scale, across every OpsChain pod and replica connecting to it, not just this one setting.
Change and workflow execution settings
dockerfile
Default value: Dockerfile
The filename to use for the Dockerfile for building the custom step runner Dockerfile or the agent image.
This setting is ignored when using an agent template or asset template Dockefile because it takes higher priority.
include_git_history
Default value: false
Whether the .git directory (including the full commit history) is included in the Git repository tarball supplied to the custom step runner Dockerfile build. The default Dockerfile does not use .git, so leaving this disabled improves build performance. Enable this if your custom Dockerfile relies on the .git directory being present, for example to run Git commands during the build.
Previously, the .git directory was always included, even though the default Dockerfile excludes it from the final image. If you have a custom step runner Dockerfile that relies on .git being present, set this to true after upgrading.
log_aggregator_additional_output_settings
Default value: not configured (the default log aggregator configuration is used)
Scope: global
Refer to the log forwarding guide for the log aggregator configuration.
on_failure.dump_properties
Default value: false
When a resource controller fails to be created during a change, OpsChain can dump the resolved resource properties to the change logs to aid debugging. Setting this to true enables the dump.
Enabling this will write resolved property values to the change logs. Sensitive values are masked unless on_failure.mask_properties is set to false.
controller.mask_properties
Default value: true
When a resource controller is constructed, the key/value pairs supplied to its constructor are checked for possibly sensitive keys and the values added to the logging data masker. Sensitive keys are those that match the regex /(?:^|_)(?:password|passphrase|passwd|secret|token|pass|key)s?(?:_|\z)/i. Setting this to false disables this masking. In conjunction with the on_failure.dump_properties setting, this can be useful for debugging controller creation issues by allowing you to see the actual property values in the change logs.
parallel_change_worker_steps
Default value: 5
The number of steps that can be run in parallel for a single change. This is only applicable when pod_per_change_step is set to false.
pod_per_change_step
Default value: false
Defines whether OpsChain will use a single Kubernetes pod for running the entire change (value set to false) or if it will create one pod for each individual step (value set to true). This setting has no effect when running a change with a MintModel.
To configure the change runner or worker image, refer to the runner image settings when this is set to true or to the worker settings when it is false.
repo_folder
Default value: .opschain
Folder in the Git repository where OpsChain properties will be imported from when running a change.
requires_approval_from
Default value: not configured (changes do not require approval)
Allows you to specify the changes that require approval before they can be executed. This setting can be configured at the project, environment and asset levels, allowing you fine-grained control of change approval. The setting accepts an array of approval rules, with each rule specifying a change_filter the user_names and/or ldap_groups that can approve changes matching the filter and an optional merge_approvers field that controls rule merging (see below)
change_filter
The change filter defines the criteria that determine if the approval rule applies to a change. The filter supports the following fields:
actions- the string constantall, or an array of action names. If the change includes at least one of the specified actions, it will match the filter.created_by- optional object restricting the filter to changes created by particular people. It accepts auser_namesarray of usernames and/or anldap_groupsarray of LDAP group names. If specified, the change must have been created by one of the listed users, or by a member of one of the listed LDAP groups, to match the filter.
An approval rule will be applied to a change if the change matches the actions and created_by criteria specified in the change_filter.
{
"change_filter": {
"actions": "all",
"created_by": { "user_names": ["john", "peter"], "ldap_groups": ["contractors"] }
}
}
created_by previously accepted a flat array of usernames (e.g. "created_by": ["john", "peter"]). That form is no longer accepted — use the user_names field instead. Settings already stored are converted automatically when upgrading, but any settings you write from now on must use the object form.
The actions array can include fully specified action names (e.g. deploy) or it can include action name patterns using the * wildcard (e.g. deploy* would match deploy, deploy_app, deploy_db, etc.).
The actions filter is only checked for changes created with an action that is available via the available actions tab for the asset.
The actions filter is matched against the change's statically defined step tree. Actions added dynamically at runtime (e.g. via append_child_steps) will not trigger approval rules.
user_names and ldap_groups
The user_names and ldap_groups fields accept an array of usernames and LDAP group names, respectively. The rule is considered authorised when any member of the ldap groups or usernames listed in the rule approves the change. Array values can also be sourced from properties using the {{property_name}} syntax, allowing you to dynamically specify approvers based on the inherited properties of the settings owner.
On an instance using LDAP authentication, every user and group name you supply — here and in created_by — is checked against the LDAP directory when the settings are saved. A name that matches no user or group is rejected, so a misspelled approver is reported at that point rather than silently never applying. A name that does match is stored using the directory's own capitalisation.
Approver names are compared without regard to case wherever they are used, so a name that differs from the directory only in capitalisation still identifies the same person or group.
If the directory cannot be reached the names are accepted as supplied and an event is recorded naming those that could not be validated.
For example:
asset settings
{
"requires_approval_from": [
{
"change_filter": {"actions": "all", "created_by": {"user_names": ["john", "peter"]}},
"user_names": ["{{metadata.environment_owner}}", "frank", "{{metadata.asset_owner}}"]
"ldap_groups": ["managers","{{metadata.approvers.ldap_groups}}"]
}
]
}
environment properties
{
"metadata": {
"environment_owner": "mary",
"approvers": { "ldap_groups": ["admin","developers"] }
}
}
asset properties
{
"metadata": {
"asset_owner": "alice"
}
}
Merged asset approval rules
The final asset level rule would be as follows:
{
"requires_approval_from": [
{
"change_filter": {"actions": "all", "created_by": {"user_names": ["john", "peter"]}},
"user_names": ["alice", "frank", "mary"],
"ldap_groups": ["admin", "developers", "managers"]
}
]
}
Ensuring all changes created by john or peter for the asset require approval from alice, frank, mary or any member of the admin, developers or managers LDAP groups.
If you require a change to be approved by two different users before starting, create two separate rules with the same change filter, each with a different username.
merge_approvers
A boolean field that controls how approval rules from overriding settings are merged together. This is best described with an example. Consider the following rules:
project settings
{
"requires_approval_from": [
{
"change_filter": { "actions": "all" },
"user_names": ["fred"]
},
{
"change_filter": { "actions": ["provision"] },
"merge_approvers": true,
"ldap_groups": ["qa"]
},
{
"change_filter": { "actions": ["destroy"] },
"merge_approvers": true,
"ldap_groups": ["admin"]
}
]
}
Example environment settings
{
"requires_approval_from": [
{
"change_filter": { "actions": "all" },
"merge_approvers": true,
"user_names": ["peter"]
},
{
"change_filter": { "actions": ["provision"] },
"ldap_groups": ["developers"]
},
{
"change_filter": { "actions": ["destroy"] },
"merge_approvers": true,
"user_names": ["mary"]
}
]
}
Example asset settings
{
"requires_approval_from": [
{
"change_filter": { "actions": "all" },
"user_names": ["john"]
},
{
"change_filter": { "actions": ["provision"] },
"ldap_groups": ["senior-developers"]
},
{
"change_filter": { "actions": ["destroy"] },
"merge_approvers": true,
"user_names": ["jane"]
}
]
}
Merged asset approval rules
The final asset level rules would be as follows:
{
"requires_approval_from": [
{(1)
"change_filter": { "actions": "all" },
"user_names": ["fred"]
},
{(2)
"change_filter": { "actions": "all" },
"merge_approvers": false,
"user_names": ["peter", "john"]
},
{(3)
"change_filter": { "actions": ["provision"] },
"merge_approvers": false,
"ldap_groups": ["qa", "developers"]
},
{(4)
"change_filter": { "actions": ["provision"] },
"ldap_groups": ["senior-developers"]
},
{(5)
"change_filter": { "actions": ["destroy"] },
"merge_approvers": true,
"user_names": ["mary", "jane"],
"ldap_groups": ["admin"]
}
]
}
The table below explains how each of the final rules is derived from the project, environment and asset level rules:
| Rule | Explanation |
|---|---|
| (1) | This rule is the project all rule. It does not allow merging so does not include any values from the environment or asset rules. |
| (2) | This rule is the environment all rule, merged with the asset all rule (combining their user_names lists) |
| (3) | This rule is the project provision rule, merged with the environment provision rule. It does not include the asset provision rule in the merged result, as the environment rule does not allow merging |
| (4) | This rule is the asset provision rule. The environment provision rule does not allow merging so this is a stand-a-lone rule. |
| (5) | This rule is the project destroy rule, merged with the environment destroy and asset destroy rules. The final rule includes all the usernames and LDAP groups from all three rules as the project and environment level rules allow merging. |
When assigning approval rules to a change, any rule that the change creator is able to approve themselves will be automatically excluded from the rules assigned to the change.
MintModel generation
enable_mintmodel_debug
Default value: false
When enabled, the MintModel generation response will include a phase_output component, providing the state of the JSON at various points throughout the generation process.
The MintModel render logs are also enriched, adding the MintPress context, the asset's properties, the filtered properties, and the MintPress properties supplied to the MintModel Steps API, making MintModel generation failures easier to diagnose.
mintmodel_render_timeout
Default value: 300
Accepted values: 300 to 1800
Scope: global, project, environment, asset
How long, in seconds, an asset's MintModel is allowed to take to render before the render is abandoned and reported as a failure.
The default of 300 seconds suits most MintModels. Raise it for an asset whose MintModel is large enough to need longer — because the setting applies at the asset level, doing so does not affect any other node. The value cannot be lowered below the default, as the render pod spends part of the allowance starting up.
This setting governs how long a single render may take. The number of renders that may run at once is governed separately by concurrent.mintmodel_limit, so raising this setting also raises how long one render can occupy a MintModel pod slot.
Parallelism settings
allow_parallel.changes
Default value: true
Scope: global, project, environment, asset
Whether to allow multiple changes to run within a single project, environment or asset. See change execution options in the changes reference guide for more information.
allow_parallel.runs_of_same_change
Default value: false
Scope: global, project, environment, asset
Whether to allow multiple changes with the same name to run in parallel within a single project, environment or asset. See change execution options in the changes reference guide for more information.
If the allow_parallel.changes setting is set to false, this setting will have no effect.
This setting applies to each change’s direct target. For example, when allow_parallel.runs_of_same_change is false changes with the same name will be allowed to run concurrently in the dev and test environments of a project (as these are two distinct targets). However, two changes with the same name will not be allowed to run concurrently in the dev environment.
You can disable parallel changes entirely by setting allow_parallel.changes to false.
allow_parallel.runs_of_same_workflow
Default value: false
Scope: global, project
Whether to allow a workflow to be run multiple times in parallel.
Git remote settings
git_remote.fetch_stale_threshold
Default value: 3 (seconds)
Scope: global
When multiple callers request a fetch for the same Git remote concurrently, OpsChain ensures only one fetch runs at a time. A caller that was waiting when the lock was released will skip the fetch — the data retrieved by the first caller is used instead.
If the wait exceeded this threshold (in seconds), OpsChain considers the completed fetch potentially stale and performs an additional fetch of its own before proceeding.
Setting this to a higher value reduces the total number of fetch operations at the cost of occasionally working with slightly older data. Setting it to 0 causes every waiter to always re-fetch after waiting.
Periodic Git remote fetches
OpsChain fetches each project's Git remotes in the background on a schedule, so its copy of a repository is already close to up to date by the time it is needed. This keeps the cost of a cold or badly out of date repository out of the critical path of creating or starting a change, generating an asset's actions, and reading properties committed to the repository. It is in addition to, not instead of, the fetch OpsChain performs when it actually needs the repository, so a longer interval does not risk a change running against an out-of-date repository.
Only active Git remotes are fetched — archived and disabled remotes are skipped. Each remote is given a small random offset within its interval so that remotes do not all fall due in the same moment.
When a fetch fails, that remote's interval doubles on each consecutive failure, up to a maximum of one day, and a warn:git_remote:periodic_fetch:failed event is recorded (at most once an hour per remote). The first fetch to succeed again resets the interval and records an info:git_remote:periodic_fetch:recovered event. An unreachable remote is therefore retried progressively less often rather than at full rate indefinitely.
git_remote.periodic_fetch_interval
Default value: 900 (15 minutes)
Accepted values: 60 or greater
Scope: global, project
How often, in seconds, each active Git remote is fetched in the background.
A shorter interval keeps repositories closer to up to date at the cost of more frequent requests to your Git server. Note that the cost of a fetch is dominated by connecting and authenticating rather than by the amount of new data, so a fetch that finds no new commits still costs about as much as one that does.
git_remote.periodic_fetch_summary.enabled
Default value: true
Scope: global
Whether OpsChain periodically records a summary of the state of background Git remote fetching, as an info:git_remote:periodic_fetch:summary event.
The summary covers all active Git remotes at the time it is written: how many there are, how many have been fetched at least once, how many are currently failing, the age of the least recently fetched remote, and the fastest, median, 95th percentile and slowest fetch durations. It is a single event covering the whole installation rather than one event per remote, so enabling it does not scale the number of events with the number of Git remotes.
Set to false to stop recording the summary entirely.
git_remote.periodic_fetch_summary.interval
Default value: 86400 (1 day)
Accepted values: 60 or greater
Scope: global
How often, in seconds, the summary event is recorded. A summary is written the next time the periodic fetch schedule runs after this interval has elapsed since the previous summary.
known_hosts
Default value: not configured (only the bundled defaults are trusted)
Scope: global
An array of additional SSH known_hosts entries to trust when connecting to SSH Git remotes. Each element is a single known_hosts line, in the format produced by ssh-keyscan. These entries are merged with the bundled known_hosts defaults; where a custom entry's host and key type match a bundled entry, the custom entry takes precedence.
{
"known_hosts": [
"git.internal.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...",
"@cert-authority *.internal.example.com ssh-rsa AAAAB3NzaC1yc2E..."
]
}
Each entry is validated when the setting is saved — an entry that is not a valid known_hosts line is rejected.
Unlike most settings, updates to known_hosts do not apply live to long-running pods. The merged file is rebuilt when the OpsChain API and worker pods start, so a restart is required for newly added entries to take effect on already-running pods. The add-git-remote request is an exception — it always validates against the current setting, so a remote can be added immediately after updating known_hosts.
git_remote.mountable
Default value: not configured (every active Git remote is available)
Scope: global, project
An allow-list of Git remote names that OpsChain will mirror into a step for the git_clone resource to check out. Only remotes whose name appears in the list are made available; an empty list makes none available.
{
"git_remote": {
"mountable": ["project-code", "app-config"]
}
}
A step that asks for a remote outside the list gets an error naming what it may use instead, for example: No git remote named "app-config" is available to this step. Available git remotes: project-code.
Leaving this setting unconfigured makes every one of the project's active Git remotes available, so the git_clone resource works without any extra configuration.
Because a Git remote is matched by name, renaming a remote that is on the list removes it from the effective allow-list until the list is updated to match — this is deliberate: an unrecognised name is treated as not allowed, rather than assumed to still refer to the same remote.
This setting can only be configured globally or on a project. An environment, an asset and an individual change cannot override it, so nothing below the project level can widen its own access to OpsChain's mirrors.
This setting controls which Git remotes OpsChain mirrors into a step — that is, which repositories a step can read without supplying any credentials of its own. That is the access OpsChain grants, and it is what the allow-list withholds.
It is not a restriction on everything a step can reach. Action code is ordinary Ruby running in the step's container, so it can always check out any repository it has credentials for — by supplying a url and credentials to the git_clone resource, or simply by running git itself. Use it to control what OpsChain hands out for free, not as a boundary around what a change can read.
It also does not protect against someone who already has permission to edit the project's settings, since they can widen the allow-list themselves.
Image build settings
OpsChain builds a container image before running each change step. On busy clusters, many image builds can be triggered simultaneously, competing for build service resources. The image build throttle limits how many builds run at the same time, queuing excess requests and starting them as running builds complete.
When a build is submitted, OpsChain checks whether a concurrency slot is available. If one is free, the build starts immediately. If all slots are occupied, the build waits and re-checks after a configurable delay. Queued builds are started in the order they were submitted (FIFO).
build_service.max_concurrent_image_builds
Default value: 8
Accepted values: 1 or greater
The maximum number of container image builds that OpsChain will run simultaneously. If more builds are submitted than this limit allows, they are queued and started in submission order (FIFO) as running builds complete.
Reduce this value to limit the load on the image build service; increase it to allow more parallel image builds on well-resourced clusters.
build_service.image_build_throttle_delay
Default value: 2
Accepted values: 1 or greater
The number of seconds to wait before re-attempting an image build that was deferred because all concurrency slots were in use (see build_service.max_concurrent_image_builds).
Decrease this value for faster slot acquisition at the cost of higher database polling frequency; increase it to reduce load when builds are frequently queued.
build_service.max_image_build_retries
Default value: 3
The number of times OpsChain will retry a container image build when a transient build service error is detected. Retries only occur for known transient errors (such as gRPC connection drops from the BuildKit service); persistent failures are not retried.
Set to 0 to disable retries entirely.
Image reuse
Rather than rebuilding an identical image for every step or change, OpsChain can reuse a previously built runner image. Each build is identified by a content key derived from the inputs that determine the image (such as the Git commit, the resolved Dockerfile, the base runner image and the build context). When a step or change would build an image whose content key matches one already in the registry, OpsChain reuses the existing image and skips the build.
Concurrent builds of the same image are also de-duplicated: when several identical builds are triggered at once, one build runs while the others wait for it and then reuse the result, rather than each rebuilding the same image. Reusable images are retained for a configurable time-to-live and are not garbage collected while still within it.
image_reuse.enabled
Default value: true
Scope: global, project, environment, asset
Whether OpsChain reuses previously built runner images. When enabled, a step or change whose content key matches an existing image reuses that image instead of rebuilding it. When disabled, an image is built for every step or change and no reusable images are retained.
image_reuse.ttl_days
Default value: 7
Accepted values: 0 or greater
Scope: global, project, environment, asset
The number of days a reusable image is retained after it was last used. Each time an image is reused its retention window is extended by this value; a shorter time-to-live never shortens the retention already granted to an existing image.
image_reuse.build_relies_on_parents
Default value: false
Scope: global, project, environment, asset
Whether the image content key includes the node's parent environment data. By default (false) parent data is excluded from the content key, which allows significantly more reuse, since most builds do not consume parent data. Set to true only when a custom dockerfile bakes parent data into an image layer, so that a change in that data correctly produces a new image.
LDAP synchronisation settings
ldap.refresh_interval
Default value: 14,400 (4 hours)
The interval between executing the LDAP synchronization process, in seconds. This process synchronizes the LDAP groups and users configured in OpsChain with the actual LDAP server.
The Security page in the OpsChain GUI allows you to trigger an immediate LDAP synchronization outside of the regular interval.
ldap.refresh_search_timeout
Default value: 300 (5 minutes)
The timeout for the LDAP synchronization search queries, in seconds. If the LDAP synchronization process is taking a long time to complete, you may want to increase this value to prevent the search queries from timing out.
Notifications
Refer to notifications for the notifications configuration.
Runner image settings
These settings modify the base FROM runner image, used to build change step runners. The runner image string is constructed in the following way:
FROM {repository}/{name}:{image_tag}
runner.image_override
Default value: not configured (there is no image override)
This setting overrides the whole image string used in the FROM directive and hence the name, repository, and image_tag settings will be ignored. This allows you to provide an alternative image registry, for example: https://customer-registry.example.com/customer/runner-image:version or ghcr.io/customer/runner-image:version.
The Kubernetes cluster running OpsChain must be able to pull images from the registry. Learn more.
The opschain-image-secret secret in the Kubernetes cluster must have credentials for the registry to allow the OpsChain build service to pull the images.
runner.image_tag
Default: Current version, e.g. 2025-01-01.
The tag of the runner image.
runner.name
Default: opschain-runner-enterprise
The name of the runner image.
runner.node_selector
Default value: (no selector)
Scope: global
Restricts the Kubernetes nodes the pods OpsChain creates may be scheduled onto - the change worker, step runner, agent, action generation and MintModel API pods. The value is a JSON object of node labels, supplied as a string:
{
"runner": {
"node_selector": "{\"opschain.io/workload\":\"runner\"}"
}
}
Use this to keep the pods that run your changes off the nodes hosting the OpsChain services themselves, or to confine them to nodes carrying the storage or licensing a change needs. An agent pod uses agent.node_selector in preference to this setting where one is configured.
The setting is read each time a pod is created, so a change takes effect without restarting the OpsChain API and applies to the next pod started.
runner.repository
Default: limepoint
The repository where the runner image is stored. Refers to a Docker registry or a container image repository.
runner.reuse_actions_rb
Default value: true
Improves change performance by only loading the actions defined in the actions.rb once.
This means that code at the top level of the file can't change - e.g. you can't define a variable at the top level and attempt to change it between steps. This can still be done within an action.
top_level_var = rand > 0.5 ? something : something_else # this wouldn't work as expected because the value wouldn't change
action :test do
var = rand > 0.5 ? something : something_else # this would work because it would be run in the action
end
This setting is ignored when pod_per_change_step is true.
runner.use_fork_for_mintpress_ctl_rb
Default value: true
Controls whether the MintModel executor runs mintpress_ctl.rb in a forked subprocess or in a new process spawned directly.
When true (the default), mintpress_ctl.rb is executed via a fork of the running executor process. Forking is both faster and more memory-efficient: the child inherits the already-loaded Ruby runtime and gem environment rather than initialising them from scratch, and the operating system's copy-on-write semantics mean that unmodified memory pages are shared with the parent process rather than duplicated. When false, mintpress_ctl.rb is spawned as a fresh process, which avoids inheriting the executor's runtime state and can be useful when the forked environment causes compatibility issues with the MintPress runtime.
This setting only applies to MintModel steps. Standard runner steps are not affected.
Pod template settings
These settings modify the Kubernetes pods OpsChain creates to run your changes. They use Kubernetes field names, and are grouped by the type of pod they apply to.
| Pod type | Pod |
|---|---|
default | Applied to every pod type below, and merged underneath the pod-type specific configuration |
change_worker | The change worker pod, used when pod_per_change_step is false |
step_runner | The step runner pod, used when pod_per_change_step is true |
agent | The agent pod |
generate_actions | The pod that derives an asset's actions from its template version |
mintmodel_api | The pod that concretises an asset's MintModel |
Not every setting applies to every pod type - each setting below lists the pod types it accepts, and configuring it against another is rejected rather than being silently ignored.
pod_templates.<pod type>.resources
Default value: not configured, except for mintmodel_api (see below)
Accepted pod types: change_worker, step_runner, agent, generate_actions, mintmodel_api
The CPU and memory a pod asks the Kubernetes scheduler for, and the ceiling it may not exceed, using Kubernetes resource quantities:
{
"pod_templates": {
"step_runner": {
"resources": {
"requests": { "cpu": "500m", "memory": "1Gi" },
"limits": { "cpu": "2", "memory": "4Gi" }
}
}
}
}
Only cpu and memory are accepted, under requests, limits or both. A value that is not a valid Kubernetes quantity is rejected when the settings are saved, rather than failing later when the pod is created.
The right values depend on your Kubernetes nodes, your concurrency limits and what your own step code does, so OpsChain does not set them for you. A pod type left unconfigured is created without requests or limits, which means the scheduler treats it as costing nothing and neither its CPU nor its memory is capped.
The mintmodel_api pod is the exception - it is configured out of the box with a memory request of 256Mi and a memory limit of 2Gi. Raise the limit for an installation with large MintModels, or lower it to fit smaller nodes. Its Java heap is sized as a percentage of the memory limit, so removing the limit leaves the JVM sizing itself against the node's memory instead.
Unlike volumes, resources can be supplied as a change level settings override, so a single large change can be given more CPU or memory without raising the limits for every other change:
opschain change create ... --settings-overrides '{"pod_templates":{"step_runner":{"resources":{"limits":{"memory":"8Gi"}}}}}'
pod_templates.<pod type>.volumes
Default value: (no volumes)
Accepted pod types: default, change_worker, step_runner, agent
volumes is accepted only on the pod types listed above — the three pods that execute your changes, plus default. Other pod types OpsChain runs internally do not mount volumes, and configuring volumes against one is rejected rather than being silently ignored. This means default.volumes applies to those three pods, not to every pod OpsChain starts.
Bind mounts a directory from the Kubernetes node's filesystem into the runner pod, so changes can reach large staging areas - installation media, for example - that are impractical to ship in a Git repository or a container image.
Volumes are configured as a map keyed by volume name:
{
"pod_templates": {
"default": {
"volumes": {
"oracle-stage": {
"host_path": { "path": "/oracle/stage" },
"mount_path": "/oracle/stage",
"read_only": true
}
}
}
}
}
| Key | Required | Description |
|---|---|---|
host_path.path | Yes | The absolute path on the Kubernetes node. The directory must already exist there |
mount_path | Yes | The absolute path inside the pod. It may differ from host_path.path |
read_only | No | Whether the mount is read only. Defaults to true |
Because volumes are keyed by name rather than listed, a lower level in the settings hierarchy can adjust a single volume without restating the others. Set a volume to null to remove one inherited from a higher level.
{
"pod_templates": {
"change_worker": {
"volumes": {
"oracle-stage": { "read_only": false },
"installers": null
}
}
}
}
The volume name must be a valid Kubernetes volume name - lowercase alphanumeric characters and -, starting and ending with an alphanumeric character.
Granting a user permission to update settings on a project, environment, asset, template or template version allows them to mount any host directory into a runner pod, which gives them access to that directory's contents on the Kubernetes node. Only grant settings update permission to users you would trust with that access.
OpsChain rejects host paths that would expose the node's container runtime or cluster data (such as /var/run and /var/lib/rancher), and mount paths that would shadow OpsChain's own mounts (such as /opt/opschain). These checks reduce the blast radius but are not a substitute for controlling who can edit settings.
volumes cannot be supplied as a change level settings override, so creating a change never grants this access.
The runner container runs as user ID 10001. Kubernetes fsGroup does not apply to host path volumes, so the directory on the node must be readable - and writable, if read_only is false - by user ID 10001. If the mount appears but its contents cannot be read, check the directory's ownership and permissions on the node.
Host paths are node local. In a multi-node cluster the directory must exist, with the correct ownership, on every node a runner pod may be scheduled onto - and on every cluster, in a multi-cluster deployment. Use runner.node_selector to restrict where runner pods are scheduled.
The host_path.path directory must already exist on the Kubernetes node before the change runs. OpsChain does not create it, so that a mistyped path fails immediately rather than silently mounting an empty directory. (The mount_path inside the pod is created by Kubernetes and does not need to exist beforehand.)
Runner pod concurrency settings
OpsChain runs each change worker, step runner, actions refresh and MintModel concretisation in its own Kubernetes pod. On busy clusters this can create more pods than the cluster can comfortably schedule at once. These settings cap how many runner, actions-refresh and MintModel pods OpsChain will run concurrently on a cluster. When a cap is reached, further pods wait and are admitted as running pods complete.
Each cap is enforced independently. A pool never borrows another pool's unused capacity, so a limit always means exactly what it says: reaching the actions-refresh cap makes further actions-refresh pods wait even while runner slots sit idle. The combined number of running pods therefore never exceeds the sum of the three limits.
concurrent.runner_limit
Default value: 15
Accepted values: 1 or greater
Scope: global
The maximum number of runner pods — change workers and per-step runners — that OpsChain will run simultaneously on a cluster.
concurrent.refresh_limit
Default value: 5
Accepted values: 1 or greater
Scope: global
The maximum number of actions-refresh pods — which derive an asset's actions for an assigned template version — that OpsChain will run simultaneously on a cluster.
concurrent.mintmodel_limit
Default value: 10
Accepted values: 1 or greater
Scope: global
The maximum number of MintModel pods — which concretise an asset's MintModel — that OpsChain will run simultaneously on a cluster.
API keys supplied to your action code
OpsChain supplies an API key to the code it runs, so that a change, an agent, or the derivation of an asset's actions can reach the OpsChain API server - for example via the query and send_email keywords.
There is nothing to configure. A key is issued automatically, carries the permissions of the user who created the change, started the agent, or triggered the actions derivation, and is revoked as soon as that work finishes.
The token.change_api_key_expiry_days and token.agent_api_key_expiry_days settings previously controlled whether these keys were issued at all, and both defaulted to 0, which disabled them. They have been removed - keys are now always issued - and are pruned from your stored settings when you upgrade.
Vault settings
Default value: not configured (will be using the default vault configuration)
Overrides the vault configuration at the node level. This is useful for when you have different vault configurations across different projects and environments (e.g. different vault config for development and production environments). When running a change, all property secrets will be decrypted using the vault settings of the change's parent.
Some examples of valid vault configurations:
{
"vault": {
"address": "http://a-vault-address",
"auth_method": "token",
"token": "token-value"
}
}
{
"vault": {
"address": "http://a-vault-address",
"auth_method": "userpass",
"user": "user",
"password": "password"
}
}
The user, password, and token fields will be automatically encrypted once the setting is saved.
vault.address
Default value: none
The address of the vault to use, for example http://vault.example.com:8200.
vault.auth_method
Default value: none
Accepted values: token, userpass, ldap
The authentication method to use.
vault.client_options
Default value: none
A JSON object containing options to use when communicating with the external vault client. Available keys include:
ssl_pem_file: The path to the SSL PEM file to use. The file must be in the image used by the OpsChain API deployment.ssl_pem_passphrase: The passphrase to use when decrypting the SSL PEM file.ssl_ca_cert: The path to the SSL certificate authority file to use. The file must be in the image used by the OpsChain API deployment.ssl_timeout: The timeout for the SSL connection in seconds.ssl_verify: Whether to verify the SSL certificate of the vault server. Defaults totrue.
The value is supplied as a string, and must be a JSON object. A value OpsChain cannot read as one is rejected when the settings are saved, rather than failing later when a secret is read or written.
vault.mount_path
Default value: none
The mount path for the KV secret store in the external secret vault.
vault.password
Default value: none
The password to use when authenticating with the external secret vault when using the userpass authentication method.
vault.password_auto_create
Default value: none
Whether OpsChain automatically generates a password when one is requested but not already present in the vault.
vault.password_include_chars
Default value: none
Whether a generated password may include alphabetic characters.
vault.password_include_numbers
Default value: none
Whether a generated password may include numeric characters.
vault.password_include_symbols
Default value: none
Whether a generated password may include symbols. The symbols used are controlled by vault.password_symbols_list.
vault.password_length
Default value: none
The length of a generated password. Must be between 1 and 100.
vault.password_must_start_with_char
Default value: none
Whether a generated password must begin with an alphabetic character.
vault.password_symbols_list
Default value: none
The set of symbol characters a generated password may draw from when vault.password_include_symbols is enabled.
vault.ssl_verify
Default value: true
Whether to verify the TLS certificate presented by the secret vault. Set to false to allow connections to a vault using a self-signed or otherwise untrusted certificate.
The password generation settings above (vault.password_*) control the format of passwords that OpsChain generates to store in the secret vault. When a setting is not configured, the vault's own default password policy applies.
vault.token
Default value: none
The token to use when authenticating with the external secret vault.
vault.username
Default value: none
The username to use when authenticating with the external secret vault when using the userpass authentication method.
vault.use_mint_encryption
Default value: none
Whether to use OpsChain's encryption to encrypt the values before storing them in the external secret vault. If this is set to true, the values will be encrypted twice.
Worker settings
These settings only apply when running a change with pod_per_change_step set to false.
remove_change_worker_pod
Default value: true
Setting that enables the change worker pod to be left running once the change finishes so we can execute into them and perform debug operations.