Skip to content
varsafe
Esc
navigateopen⌘Jpreview
On this page

CI/CD Pipelines

Authenticate pipelines with a team-scoped API token and inject secrets into deploy commands without writing .env files or leaking values into job logs.

CLI v7.2.7

By the end of this page your pipeline authenticates with a team-scoped API token and injects secrets directly into your deploy command’s process environment — no .env file in the repository, no secrets on the runner’s disk, nothing echoed to the job log.

Prerequisites

  • A varsafe project containing the secrets your pipeline needs
  • A team role that can create API tokens
  • Access to your CI system’s secret store (GitLab CI/CD variables or GitHub Actions secrets)

1. Create a CI token

  1. In the dashboard, open your team settings and go to API Tokens
  2. Click Create API Token and name it after the pipeline that will use it (e.g. github-actions-production-deploy)
  3. Under Access, choose Read-only unless the pipeline genuinely writes secrets — a deploy job that only injects configuration never needs write. See access level
  4. Copy the vs_at_... token immediately — it is shown only once

API tokens are scoped to the team they were created in: a leaked token can never reach another team’s secrets. For least privilege, create one token per pipeline rather than sharing a single token — token names appear in the audit log, so per-pipeline tokens let you trace every action back to the job that performed it.

A read-only token narrows the blast radius further: if it leaks, an attacker can read what that pipeline could read, but cannot rotate or delete your secrets. An existing token can be changed to read-only later without a rotation window, so this is worth checking on tokens you already have.

2. Store the token in your CI secret store

Go to Settings → CI/CD → Variables and add a variable:

Key:   VARSAFE_TOKEN
Value: vs_at_...
Flags: Masked, Protected

Masked keeps the value out of job logs; Protected restricts it to protected branches and tags, so a merge request from a fork cannot exfiltrate it.

Go to Settings → Secrets and variables → Actions and click New repository secret:

Name:  VARSAFE_API_TOKEN
Value: vs_at_...

GitHub Actions automatically masks registered secrets in log output.

3. Authenticate in the job

Set VARSAFE_API_TOKEN (or the shorter alias VARSAFE_TOKEN) in the job environment. Every varsafe command picks it up automatically. There is no login step:

export VARSAFE_TOKEN="$CI_VARSAFE_TOKEN"
varsafe run -i "$VARSAFE_PROJECT_ID" -e production -- ./deploy.sh

Because there is no login step, an invalid token first surfaces on the command that needs it. To fail fast on a revoked or mistyped token, put a cheap authenticated call at the top of the job:

varsafe whoami

4. Pin the project with --project-id

CI jobs are non-interactive: when the CLI cannot prompt, it needs the project and environment stated explicitly. Use -i/--project-id rather than -p/--project — the ID is a stable UUID that survives project renames:

varsafe run -i "$VARSAFE_PROJECT_ID" -e production -- ./deploy.sh

To find the ID, run varsafe use locally inside your repository — it writes the selected projectId into a .varsafe file at the repo root (IDs only, no secrets). Store the ID as a plain CI variable (it is not sensitive).

5. Wrap the deploy command with varsafe run

varsafe run fetches secrets, injects them into the child process environment, and passes the command’s arguments through exactly as written. The secrets exist only in that process — they never touch the runner’s filesystem:

varsafe run -i "$VARSAFE_PROJECT_ID" -e production -- docker compose up -d

Narrow what gets injected with --include glob patterns:

varsafe run -i "$VARSAFE_PROJECT_ID" -e production --include 'DB_*,API_*' -- ./deploy.sh

Complete examples

GitLab CI

# Inject secrets into a GitLab CI job with an API token.
# VARSAFE_TOKEN is a masked, protected CI/CD variable.
deploy:
  image: oven/bun:1
  script:
    - curl -fsSL https://varsafe.dev/install.sh | bash
    - export PATH="$HOME/.varsafe/bin:$PATH"
    # No login step: every command reads VARSAFE_TOKEN from the job environment.
    # Passing a token inline (`--token "$VARSAFE_TOKEN"`) is refused — argv leaks
    # to shell history and process listings.
    - varsafe run -i "$VARSAFE_PROJECT_ID" -e production -- bun run deploy

GitHub Actions

# Inject secrets into a GitHub Actions job with an API token.
# VARSAFE_API_TOKEN is a repository secret.
name: deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install varsafe
        run: |
          curl -fsSL https://varsafe.dev/install.sh | bash
          echo "$HOME/.varsafe/bin" >> "$GITHUB_PATH"
      - name: Deploy with secrets
        env:
          VARSAFE_TOKEN: ${{ secrets.VARSAFE_API_TOKEN }}
        # No login step: every command reads VARSAFE_TOKEN from the job environment.
        # Passing a token inline (`--token "$VARSAFE_TOKEN"`) is refused — argv leaks
        # to shell history and process listings.
        run: |
          varsafe run -i "${{ vars.VARSAFE_PROJECT_ID }}" -e production -- npm run deploy

When the pipeline needs a file

Prefer varsafe run — most tools read configuration from the environment. When a consumer genuinely requires a file (e.g. docker compose --env-file), export one, keep it in RAM, and delete it in the same command chain:

varsafe export -i "$VARSAFE_PROJECT_ID" -e production --plain --tmpfs -o app.env
docker compose --env-file /dev/shm/app.env up -d
rm -f /dev/shm/app.env
  • --tmpfs writes to /dev/shm/ (RAM-backed), so the plaintext never reaches persistent disk
  • --plain is needed here because docker compose --env-file reads the file itself. varsafe export encrypts .env by default and that works fine under a token — but nothing in CI can decrypt it, since the private key is restricted to an interactive Owner/Admin session. Encrypt when the artifact is for a human to use later; use --plain when a tool in the same job has to read it. See encrypted .env.

Keep secrets out of job logs

  • Register the token as a masked variable so the CI system redacts it if a command prints it
  • Never echo injected variables, and avoid varsafe list -r or varsafe get in job scripts — their output lands in the log
  • Be careful with set -x (shell tracing): it prints expanded command lines, including any secret passed as an argument

Self-hosted or staging API

Set VARSAFE_API_URL to point the CLI at a different API instance — it takes priority over the URL saved at login:

VARSAFE_API_URL=https://varsafe.internal.example.com varsafe run -i "$VARSAFE_PROJECT_ID" -e production -- ./deploy.sh

Verify it works

  • Add varsafe whoami after the auth step — it confirms the token is valid before the deploy runs
  • Check the Audit Log in the dashboard: every fetch performed with the token is recorded, with the token identified as the actor

Troubleshooting

  • Invalid or expired token — the token was revoked or rotated, or a stale saved context points at a team the token cannot access. See Invalid or expired token.
  • Project is required. Use -p <project> or -i <project-id> to specify. — the job is non-interactive and no project was given. Add -i "$VARSAFE_PROJECT_ID" (and -e for the environment) to the command.
  • More failure modes: Troubleshooting.