Post

Secrets Management for Small Teams: AWS Secrets Manager + CI

Secrets Management for Small Teams: AWS Secrets Manager + CI

Secrets in repos are a reliability problem and a security risk. A simple secrets manager plus a minimal CI integration eliminates most of the pain without adding heavy process.

This post shows a small-team approach using AWS Secrets Manager and a CI pipeline.

Context

Problem: Teams rely on hardcoded secrets and inconsistent handling. Approach: Centralize secrets, use least-privilege access, and fetch at runtime with OIDC. Outcome: Fewer leaked credentials and repeatable deployments.

Create and name secrets consistently

Use clear names and separate environments. Keep secrets in one place.

1
2
3
aws secretsmanager create-secret \
  --name prod/app/config \
  --secret-string '{"DATABASE_URL":"postgres://..."}'

Store related values together so you can rotate them as a unit.

Grant least-privilege access

Scope access to the smallest set of secrets and use a dedicated role for CI.

1
2
3
4
5
6
7
8
9
10
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue"],
      "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/app/*"
    }
  ]
}

If you use GitHub Actions, create a role that can only be assumed by your repo.

Fetch secrets in CI

Use OIDC to avoid long-lived AWS keys. Then pull the secret at runtime.

1
2
3
4
5
6
7
8
9
10
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789012:role/gh-secrets-read
    aws-region: us-east-1
- name: Fetch secrets
  run: |
    SECRET=$(aws secretsmanager get-secret-value \
      --secret-id prod/app/config \
      --query SecretString --output text)
    echo "APP_CONFIG=$SECRET" >> $GITHUB_ENV

Inject only what you need for that job, then let the job exit.

Use secrets safely in the app

Parse secrets from environment variables at startup. Avoid writing them to disk.

1
export APP_CONFIG='{"DATABASE_URL":"postgres://..."}'

If your runtime needs individual values, parse JSON and map to env vars in a launch script.

Rotate secrets deliberately

Set a rotation schedule and test it. Secrets Manager can call a Lambda function to rotate database or API keys. Even if you rotate manually, track the last rotation date and make it routine.

Local development

Use a local .env file and keep it out of source control. Match the structure of production secrets so the app behaves the same way in dev.

Takeaways

Centralize secrets, scope access, and fetch them at runtime. A small workflow like this removes hardcoded credentials and scales well as your team grows.

This post is licensed under CC BY 4.0 by the author.