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
- In the dashboard, open your team settings and go to API Tokens
- Click Create token and name it after the pipeline that will use it (e.g.
github-actions-production-deploy) - 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, ProtectedMasked 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 login —
varsafe 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 aliasVARSAFE_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 deployGitHub 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 deployWhen 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
--tmpfswrites to/dev/shm/(RAM-backed), so the plaintext never reaches persistent diskvarsafe exportencrypts.envoutput by default — use--plainonly 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
echoinjected variables, and avoidvarsafe list -rorvarsafe getin 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 whoamiafter 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-efor the environment) to the command.- More failure modes: Troubleshooting.