Files
development-gitops-argo-cd-…/references/environments.md
oleg-lukasonokandClaude Opus 5 50d746d03f Add gitops-argo-cd agent skill
Consolidates the five cogarchhubgitops-* source skills into one skill with
scope routing: ArgoCD Applications, Copier templates, Helm values, environment
answer files, and the end-to-end resource-addition walkthrough.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 23:48:32 +03:00

8.4 KiB

Environment Configuration

Per-environment copier-answers.yaml files: feature flags, image tags, global application settings, drift between environments, and re-rendering with copier update.

Read this when enabling or disabling a resource for one environment, onboarding a new environment, auditing what is switched on, or investigating why two environments differ.


1. How environments work

Each deployed environment is a rendered copy of the template repository. The rendering is driven by an answer file supplying environment-specific values for the variables declared in copier.yaml:

Template repo
    + <env>/copier-answers.yaml
    ─────────────────────────────
    → rendered cluster state (what ArgoCD reads)

The rendered state normally lives on a separate branch or repository per environment. The answer files themselves live at <env>/copier-answers.yaml in the template repo.


2. Answer file structure

Copier maintains two metadata fields automatically; everything else is user-defined:

_src_path: ../                    # relative path to the template source
_commit: <git-commit-sha>         # template commit used for the last render

gitops:
  repo_url: https://github.com/org/repo
  branch: main

operators:
  is_enabled: true
  eso_is_enabled: true
  cnpg_is_enabled: false
  redis_is_enabled: false

configuration:
  is_enabled: true
  cluster_secret_store_is_enabled: true

applications:
  is_enabled: true
  global:
    is_internal: "false"
    ca_env: production
    host: myapp.example.com

Keys must match copier.yaml exactly. Copier silently ignores unknown keys — a typo does not error, it just leaves the variable at its default, which is the most common cause of a "why is this environment misconfigured" investigation.


3. Variable categories

Feature flags

Flags follow <domain>.is_enabled and <domain>.<sub>_is_enabled:

Variable Effect when true
operators.is_enabled Renders the 200-operators band
operators.eso_is_enabled Renders ESO operator group, subscription, configuration
operators.cnpg_is_enabled Renders CNPG operator group and subscription
operators.redis_is_enabled Renders Redis operator group and subscription
configuration.is_enabled Renders the 300-configuration band
configuration.cluster_secret_store_is_enabled Renders ClusterSecretStore
databases.is_enabled Renders the 400-databases band
advantage_mcp.is_enabled Renders the 700-advantage-mcp band
applications.is_enabled Renders the 800-applications band

Parent flags gate whole bands. With operators.is_enabled: false, every sub-flag under operators is irrelevant — the entire 200-operators directory is omitted regardless of what the sub-flags say. When a resource unexpectedly fails to render, check the parent flag before the specific one.

Image variables

<domain>:
  <service>:
    image:
      address: <registry>/<image-name>
      tag: <semver-or-sha>

Every enabled workload needs non-empty values for both.

Global application settings

applications.global carries settings shared by all services in the release:

applications:
  global:
    is_internal: "true"          # string bool
    enable_instana: "false"      # string bool
    ca_secret_name: ca-secret
    ca_env: <env-name>           # dev | staging | production
    maintenance: "false"         # string bool
    host: <public-hostname>
    ca_host: <internal-hostname>
    suffix_url: <path-suffix>
    node_selector_app: <node-label>
    mongo:
      host: <mongodb-host>
      port: "27017"
      user: <mongodb-user>
    redis:
      host: <redis-host>
      port: "6379"
      max_retries: "3"
    neo4j:
      url: <neo4j-bolt-url>
      user: <neo4j-user>

4. Booleans: two different kinds

This trips people up constantly:

Kind Written as Why
Copier feature flag (*_is_enabled) YAML boolean true / false Evaluated as a Jinja2 conditional deciding whether a directory renders
Helm string-bool (is_internal, maintenance, enable_instana) Quoted string "true" / "false" The values templates emit them as strings and the chart reads them as strings

A feature flag written as "false" is a non-empty string — truthy in Jinja2 — so the resource renders when you meant to disable it. This fails silently and deploys something.


5. Rules

Answer files

  • _src_path and _commit are Copier-managed; never edit them by hand.
  • Keep answer files minimal — only override what differs from the copier.yaml default.
  • Never commit real secrets. Use placeholders and inject at runtime via ExternalSecret.

Multi-environment

  • One answer file per environment; never share a file across environments.
  • When adding a variable to copier.yaml, update every answer file needing a non-default value — the rest correctly fall back to the default.

Enabling a resource

  1. Set the flag to true.
  2. Supply all required image and configuration variables for that resource.
  3. Re-render the environment.
  4. Review the rendered diff before committing.

Skipping step 2 renders a values file with empty fields; Helm then fails at sync with an error that names the chart, not the answer file, which makes it slow to trace back.

Disabling a resource

  1. Set the flag to false.
  2. Re-render.
  3. ArgoCD prunes the Application and its cluster resources on the next sync, because prune: true is set. This is destructive and immediate — confirm the resource is really meant to leave the cluster, not just be paused.

6. Workflows

New environment

mkdir <new-env>

# Option A — interactive prompts
copier copy . /tmp/gitops-<new-env> --answers-file <new-env>/copier-answers.yaml

# Option B — start from the nearest existing environment
cp <base-env>/copier-answers.yaml <new-env>/copier-answers.yaml
# then edit for environment-specific values

Option B is faster but carries over every override from the source environment; walk the whole file rather than only the fields you came to change.

Re-render after a template change

copier update /path/to/rendered-<env> \
  --answers-file <env>/copier-answers.yaml \
  --overwrite

git -C /path/to/rendered-<env> diff

Compare two environments

copier copy . /tmp/env-a --overwrite --answers-file env-a/copier-answers.yaml
copier copy . /tmp/env-b --overwrite --answers-file env-b/copier-answers.yaml
diff -rq /tmp/env-a/cluster /tmp/env-b/cluster

Audit which resources are enabled

python3 -c "
import yaml, sys
a = yaml.safe_load(open(sys.argv[1]))
def walk(d, prefix=''):
    for k, v in (d.items() if isinstance(d, dict) else []):
        if k.endswith('_is_enabled') or k == 'is_enabled':
            print(f'{prefix}{k}: {v}')
        elif isinstance(v, dict):
            walk(v, prefix + k + '.')
walk(a)
" <env>/copier-answers.yaml

7. Validation

# Render the environment
copier copy . /tmp/gitops-validate --overwrite --answers-file <env>/copier-answers.yaml

# No .jinja may leak through
find /tmp/gitops-validate -name "*.jinja" | wc -l   # must be 0

# Every rendered YAML parses
find /tmp/gitops-validate -name "*.yaml" -exec \
  python3 -c "import yaml, sys; yaml.safe_load(open(sys.argv[1])); print('OK:', sys.argv[1])" {} \;

# Every Application has its resources directory
for app in $(find /tmp/gitops-validate/cluster/argocd -name "application.yaml" -exec \
    python3 -c "import yaml,sys; print(yaml.safe_load(open(sys.argv[1]))['spec']['source']['path'])" {} \;); do
  [ -d "/tmp/gitops-validate/$app" ] || echo "MISSING resources dir: $app"
done

8. Mistakes to avoid

  • Editing _src_path or _commit — breaks copier update.
  • Enabling a flag without supplying the resource's image and configuration variables.
  • Writing a feature flag as "true" instead of true — truthy either way, so it renders when disabled was intended.
  • Editing an answer file without re-rendering and reviewing the diff.
  • Setting applications.global addresses (host, mongo, redis, neo4j) without confirming the environment's real infrastructure — wrong values misconfigure every service silently.
  • Putting environment secrets in the answer file rather than an external secret manager.
  • Assuming a sub-flag controls a resource when its parent band flag is false.