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.

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 token and name it after the pipeline that will use it (e.g. github-actions-production-deploy)
  3. 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.

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

Two equivalent options:

  • Explicit loginvarsafe login --token "$VARSAFE_TOKEN" validates the token up front, so a revoked or mistyped token fails the job early with a clear error instead of mid-deploy.
  • Environment variable only — set VARSAFE_API_TOKEN (or the shorter alias VARSAFE_TOKEN) in the job environment and skip the login step entirely; every varsafe command picks it up automatically.

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

Both files below are tested configurations from this repository.

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"
    - varsafe login --token "$VARSAFE_TOKEN"
    - 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 }}
        run: |
          varsafe login --token "$VARSAFE_TOKEN"
          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
  • varsafe export encrypts .env output by default — use --plain only when the consumer cannot read varsafe’s encrypted .env format

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.