CI/CD pipelines need credentials: SSH keys to deploy to your VPS, registry tokens, cloud API keys, and database passwords for tests. GitHub Actions secrets store these encrypted, and expose them only to the workflow runs that need them. Used well, they keep credentials out of your code. Used carelessly, they can still leak.

Types of secrets

Scope Where to set Use
Repository Repo → Settings → Secrets and variables → Actions Most projects
Environment Repo → Settings → Environments → (env) Per-environment values with protection rules (e.g. production)
Organization Org settings Shared across selected repositories

Non-secret settings (hostnames, region names) belong in variables (vars.NAME), which are visible in logs.

Add and use a secret

With the GitHub CLI:

gh secret set VPS_SSH_KEY < ~/.ssh/deploy_key
gh secret set VPS_HOST --body "203.0.113.10"
gh secret set DB_PASSWORD --env production
gh secret list

In a workflow:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production          # uses environment secrets + protection rules
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        env:
          SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
          HOST: ${{ secrets.VPS_HOST }}
        run: |
          install -m 600 /dev/null ~/.ssh_key
          printf '%s\n' "$SSH_KEY" > ~/.ssh_key
          ssh -i ~/.ssh_key -o StrictHostKeyChecking=accept-new deploy@"$HOST" 'cd /opt/app && docker compose pull && docker compose up -d'

Pass secrets through env:, not directly into run: scripts with ${{ }}. That avoids shell-injection problems with special characters. See GitHub Actions deploy to VPS.

Protect production with environments

Environments add:

  • required reviewers: a person must approve deployments;
  • wait timers and deployment branch rules (only main or tags can deploy);
  • environment-specific secrets, available only to jobs that target that environment.

That stops a random branch, or a pull request, from deploying to production.

Use a dedicated, limited deploy key

  • Create an SSH key only for CI, and add it to a dedicated deploy user on the VPS, not root. See SSH keys.
  • Restrict what it can do: a forced command in authorized_keys, from="…" with runner IP ranges (hard with GitHub-hosted runners), or sudo rules limited to the deploy script. See sudo and sudoers.
  • Pin the server’s host key in a secret or known_hosts file instead of disabling host key checking.

Prefer OIDC over long-lived cloud keys

For cloud providers that support it (AWS, Azure, GCP, and others), GitHub’s OIDC integration issues short-lived credentials per run. No static keys to leak:

permissions:
  id-token: write
  contents: read

Then configure the provider to trust tokens from your repository and branch.

How secrets leak (and how to prevent it)

  • Logs: GitHub masks exact secret values in logs, but not transformed ones (base64, substrings). Don’t echo secrets, or run commands in debug mode that print them.
  • Pull requests from forks don’t receive secrets by default. Keep it that way. Be careful with pull_request_target workflows, which run with secrets on untrusted code.
  • Third-party actions run with access to your environment. Pin them to a full commit SHA (uses: owner/action@<sha>) for sensitive workflows.
  • Artifacts and caches: don’t write secret files into uploaded artifacts.
  • Set minimal permissions: for GITHUB_TOKEN (default to read-only).

If a secret leaks: rotate it immediately (generate a new key or token, update the secret, and revoke the old one). See secrets management.

Self-hosted runners

Secrets are available on whatever machine runs the job. Use self-hosted runners only for private repositories, and keep them isolated. See self-hosted GitHub runner.

Quick reference

Task Command / syntax
Set secret gh secret set NAME
Environment secret gh secret set NAME --env production
Use in workflow env: X: ${{ secrets.NAME }}
Non-secret config vars.NAME
Protect deploys environment: production + required reviewers
No static cloud keys OIDC (id-token: write)

Related: GitHub Actions Docker builds and Ansible Vault.

  • GitHub Actions
  • Security
  • CI/CD