AtomLearnAtomLearn
DashboardGoalsPathAchievementsReviewSign In
DashboardCI/CDSecrets & Environments
CI/CDNot Started

Secrets & Environments

Safely use API keys, passwords, and deploy tokens in CI/CD without leaking them

0%
Knowledge0%
Learn & Drill
Fluency0%
Drill & Speed
Retention0%
Mastery & Review
Confidence0%
All modes
Practice
Knowledge
Fluency
Retention

Knowledge Debt detected

You can study this freely — but your score may plateau if these foundations have gaps. The Mastery badge requires them to be solid.

Explanation

CI/CD pipelines need credentials — database passwords, cloud API keys, deploy tokens — but these must never appear in source code or logs. GitHub Actions provides secrets to solve this.

GitHub Secrets — the safe way:

yaml # Add secrets: GitHub repo → Settings → Secrets and variables → Actions → New secret # Name: STRIPE_SECRET_KEY # Value: sk_live_abc123... # Reference in workflow with ${{ secrets.NAME }} jobs: deploy: runs-on: ubuntu-latest steps: - run: | curl -X POST https://api.stripe.com/v1/charges \ -u ${{ secrets.STRIPE_SECRET_KEY }}: GitHub automatically **masks** secrets in logs — they appear as ***` if accidentally printed.

Environment-level secrets (staging vs prod):

yaml # GitHub: Settings → Environments → Create "staging" and "production" # Add different secrets to each environment jobs: deploy-staging: environment: staging # uses staging secrets runs-on: ubuntu-latest steps: - run: deploy --url ${{ secrets.DEPLOY_URL }} deploy-prod: environment: production # uses production secrets, may require approval needs: deploy-staging runs-on: ubuntu-latest steps: - run: deploy --url ${{ secrets.DEPLOY_URL }}

Regular variables (non-secret config):

yaml env: NODE_ENV: production PORT: 3000 jobs: build: env: VITE_API_URL: https://api.myapp.com steps: - run: npm run build env: VITE_ANALYTICS_ID: GA-12345

OIDC — keyless cloud authentication (best practice):

yaml # Instead of storing long-lived AWS access keys as secrets, # configure GitHub's OIDC provider to generate short-lived tokens permissions: id-token: write contents: read steps: - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789:role/github-actions aws-region: us-east-1 # Now you have AWS access without any stored keys!

Security rules:

  • Never hardcode credentials in workflow files
  • Never echo a secret (even masked, avoid the habit)
  • Use environment-level secrets to separate staging/prod access
  • Rotate secrets regularly — treat them like passwords
  • Use OIDC/short-lived tokens instead of long-lived keys when possible

Examples

Deploy to a server using SSH key secret

The SSH private key is stored as a GitHub Secret — it never appears in code or logs.

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up SSH key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          ssh-keyscan -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts

      - name: Deploy via rsync
        run: |
          rsync -avz --delete ./dist/ \
            -e "ssh -i ~/.ssh/deploy_key" \
            deploy@${{ secrets.SERVER_HOST }}:/var/www/app/

      - name: Restart app
        run: |
          ssh -i ~/.ssh/deploy_key deploy@${{ secrets.SERVER_HOST }} \
            "sudo systemctl restart myapp"

Build and push a Docker image to a registry

Tagging with the git SHA means every deployed image is traceable back to the exact commit that built it.

# .github/workflows/docker.yml
name: Docker Build and Push

on:
  push:
    branches: [main]

jobs:
  build-push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: |
            myusername/myapp:latest
            myusername/myapp:${{ github.sha }}

How well did you understand this?

Next in CI/CD

Building a CI Pipeline

Continue