Docs / CI/CD & Kubernetes

CI/CD and Kubernetes

Give each deployment a read-only process token, then choose whether resolved values belong in later CI steps, one command, or a temporary file.

GitHub Actions

Store the process token as the repository or environment secret DYNV_TOKEN. Set the required API URL with the action's url input. The action installs dynv from https://dynv.dev/install.sh, which verifies the binary against SHA256SUMS before running it.

.github/workflows/test.yml
name: test
on: [push]
permissions:
  contents: read
jobs:
  test:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: dynv/action@v1
        with:
          token: ${{ secrets.DYNV_TOKEN }}
          url: https://api.dynv.dev
          process: api
          mode: run
          cmd: npm test

run is the default recommendation: values are available to that command, not exported to every later step. The command runs through Bash with -e and pipefail, and its exit code becomes the step's exit code.

Export variables or a temporary file

env mode writes every resolved variable to GITHUB_ENV for later steps and calls ::add-mask:: for each value, including multiline values. Any later step can read these secrets, so use run when you do not need job-wide export.

Export to later steps
- uses: dynv/action@v1
  with:
    token: ${{ secrets.DYNV_TOKEN }}
    url: https://api.dynv.dev
    process: api
    mode: env
- run: npm test

file mode creates a dotenv file with mode 0600. The action's post step removes that same file at job cleanup, without following a replacement symlink.

Temporary dotenv file
- uses: dynv/action@v1
  with:
    token: ${{ secrets.DYNV_TOKEN }}
    url: https://api.dynv.dev
    process: api
    mode: file
    path: .env.ci
- run: npm test

GitLab CI

Add DYNV_TOKEN as a masked, protected CI/CD variable and DYNV_URL as a variable. This snippet keeps installation in the job workspace and scopes values to the test command:

.gitlab-ci.yml
variables:
  DYNV_URL: https://api.dynv.dev
  DYNV_INSTALL_DIR: "$CI_PROJECT_DIR/.dynv-bin"
  DYNV_PROCESS: api

before_script:
  - curl -fsSL https://dynv.dev/install.sh -o /tmp/dynv-install.sh
  - sh /tmp/dynv-install.sh
  - export PATH="$DYNV_INSTALL_DIR:$PATH"

test:
  script:
    - dynv run --require -- npm test

Use protected branches or environments to limit which jobs receive the token. Revoke and replace it in the Dynv dashboard if the CI project's token is exposed.

Kubernetes init container

An init container installs the verified CLI into a shared emptyDir. The app container starts through dynv run and keeps its ciphertext-only rollback/offline cache in another emptyDir. Replace the image and args with your application's normal start command.

deployment.yml
apiVersion: v1
kind: Secret
metadata:
  name: api-dynv
type: Opaque
stringData:
  token: dynv_pt_REPLACE_ME
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 1
  selector:
    matchLabels: { app: api }
  template:
    metadata:
      labels: { app: api }
    spec:
      volumes:
        - name: dynv-bin
          emptyDir: {}
        - name: dynv-cache
          emptyDir: {}
        - name: dynv-token
          secret:
            secretName: api-dynv
            defaultMode: 0400
      initContainers:
        - name: install-dynv
          image: curlimages/curl:8.12.1
          command: ["/bin/sh", "-c"]
          args:
            - curl -fsSL https://dynv.dev/install.sh -o /tmp/install.sh
              && DYNV_INSTALL_DIR=/dynv sh /tmp/install.sh
          volumeMounts:
            - { name: dynv-bin, mountPath: /dynv }
      containers:
        - name: api
          image: your-registry.example/api:replace-me
          command: ["/dynv/dynv", "run", "--require", "--cache", "/dynv-cache/c", "--"]
          args: ["node", "server.js"]
          env:
            - { name: DYNV_URL, value: https://api.dynv.dev }
            - { name: DYNV_PROCESS, value: api }
            - { name: DYNV_TOKEN_FILE, value: /var/run/secrets/dynv/token }
          volumeMounts:
            - { name: dynv-bin, mountPath: /dynv }
            - { name: dynv-cache, mountPath: /dynv-cache }
            - { name: dynv-token, mountPath: /var/run/secrets/dynv, readOnly: true }

A sidecar cannot put environment variables into another container, so wrapping the app command is required. The Kubernetes Secret contains the Dynv process token, not the resolved values; like any Kubernetes Secret it is stored by your cluster (normally base64-encoded in etcd), so enable etcd encryption and restrict RBAC appropriately. The Dynv API still sees only ciphertext.

Use one token per Deployment and DYNV_PROCESS to pin the expected service. --require needs a successful initial live fetch: it refuses cache-only and inherited-only startup. The --cache file still provides rollback protection, and it survives app-container restarts but not pod replacement.