Environment variables in a preview environment

A developer creates an environment. Its services find each other and know their own preview URL without anyone editing a variable. This is how.

Nothing here involves a model or a guess. Every rule is deterministic and narrow. A rewrite that is usually right is worse than none because it moves traffic somewhere nobody asked for and nobody can see.

An application runs in one of two modes and the variables work differently in each:

Full mode

1. Addresses that name another service are repointed. Every Service is renamed for the environment so a chart that calls http://collaboration-server-service:9600 would reach nothing. Nezal moves those to the same environment's copy:

What Becomes in the environment amr
<service> <service>-preview-amr
<service>.<namespace> and <service>.<namespace>.svc and the full cluster name <service>-preview-amr
Anything else Unchanged

A value is only rewritten when the whole value is an address. A URL inside a JSON document or a list is left alone because editing inside a format Nezal does not parse risks corrupting a value that was working. A connection string such as redis://cache:6379 is a whole-value URL so only its host moves.

References to ConfigMaps and Secrets and volume claims and image pull secrets are pointed at the environment's own copies in the same way.

2. nezal.env for everything else. One file in the nezal/ folder with fixed values and templates such as {{ .Environment }} and {{ .Service.cache.Host }}. A {{ .Secret.NAME }} becomes a reference to the application's Secret and its value is never read into a variable. The nezal folder guide has the syntax and every template.

Precedence in full mode. Lowest to highest. The later layer wins by variable name.

  1. The environment your chart or manifests declare with its addresses repointed
  2. What Nezal sets in every copy (below)
  3. nezal.env from the environment's branch

Shared mode

1. Short names already work. Inside an environment namespace every service of the application exists under its own short name. A service you overrode is a real copy. Every other one is an alias pointing at the shared baseline. So a service calling http://ledger:8080 reaches your ledger when you are running one and the shared ledger when you are not. Nothing is rewritten and nothing needs to be.

2. Addresses that name the baseline are repointed. A chart that sets LEDGER_URL=http://ledger.payments.svc.cluster.local:8080 skips the alias and reaches the shared instance even when you are running your own. Nezal moves those. The rules are exactly these and nothing else:

What Becomes Why
<service>.<app-namespace>.svc.<cluster-domain> <service> The alias then resolves it in this environment
<service>.<app-namespace>.svc <service> Same with the shorter form
<service>.<app-namespace> <service> Same with the shortest form
The application's public host This environment's preview host A browser-facing URL should reach your copy
Anything else Unchanged Not ours to move

A value is only rewritten when the whole value is an address. A URL inside a JSON document or a list is left alone for the same reason as in full mode. A connection string such as postgres://user:[email protected]:5432/shop is a whole-value URL so only its host moves and the credentials and database name are carried through untouched.

3. One optional file for what is left. A feature flag or a path prefix or an external endpoint that differs per branch. Nezal cannot infer these and does not try.

The file

nezal/environment.yaml at the root of your repository is read from the branch the environment was created from. A missing file is the normal case and means nothing.

version: 1
services:
  frontend:
    env:
      FEATURE_NEW_CHECKOUT: "true"
      PUBLIC_API_URL: "{{ .PreviewURL }}/api"
      BACKEND: "{{ .Service.backend.URL }}"
  backend:
    envFrom:
      DATABASE_URL:
        secretKeyRef:
          name: shop-db
          key: url

It is keyed by branch rather than by developer on purpose. You edit it on your feature branch and it applies to your environment and to a pull-request environment for that branch with nobody typing anything. A file named after a person drifts and leaks personal settings into the shared tree. It also does nothing for a pull request that has no person attached.

What a template may refer to:

Reference Is
{{ .Environment }} The environment name
{{ .Owner }} Who owns it
{{ .Ref }} The branch it was created from
{{ .Namespace }} The Kubernetes namespace holding the copies
{{ .PreviewURL }} The full preview URL
{{ .PreviewHost }} Just the host
{{ .Domain }} The ingress domain
{{ .Service.<name>.URL }} How to reach that service from inside this environment
{{ .Service.<name>.Host }} Its short name
{{ .Service.<name>.Port }} Its first declared port

A name that does not exist is an error rather than an empty string. The alternative prints <no value> into a live variable and sends somebody chasing a URL that is literally that.

Secrets never go in this file. It is committed to git and readable by everyone who can clone the repository. A plaintext value whose name looks like a credential is refused. Use envFrom with secretKeyRef or configMapKeyRef for anything sensitive.

Precedence in shared mode. Lowest to highest. The later layer wins by variable name.

  1. The baseline's own environment as your chart or manifests declare it
  2. Addresses repointed at this environment
  3. What Nezal sets in every copy (below)
  4. nezal/environment.yaml from your branch
  5. What you set on the environment in the dashboard or with nezal up --set

What Nezal sets in every copy

These are set in both modes below anything you set yourself so your own value always wins. They are a contract: a name that ships is never renamed or removed.

Variable Is
NEZAL_ENVIRONMENT The environment name
NEZAL_OWNER Who owns it
NEZAL_REF The branch it was created from
NEZAL_NAMESPACE The namespace holding the copies
NEZAL_PREVIEW_URL The full preview URL. Empty when no ingress domain is configured
NEZAL_PREVIEW_HOST Just the host
NEZAL_ROUTING_HEADER X-Nezal-Env. The header to forward when calling another service through the ingress in shared mode

The environment page shows the result with the source of every value so you can see which layer produced what. Every rewrite is listed there with the value it replaced and the rule that matched. Nothing moves invisibly.

One application with many environments

Ten developers on ten branches is one application and ten environments of it and not ten applications. An application is the thing that is connected to a repository. An environment is one developer's copy of it. Connecting the same repository twice at the same path is almost always a wanted environment and Nezal says so when you do it.