Migrate to GitHub Actions Secrets
Upload secrets from your .env file directly into GitHub Actions repository secrets using evnx migrate.
Prerequisites
Migrate to GitHub Actions Secrets
evnx migrate --to github-actions is the only destination where evnx uploads secrets for you directly — no copy-pasting commands. It calls the GitHub REST API, encrypts each value with the repository's public key, and streams results with a live progress bar.
Feature flag required. GitHub Actions migration is only available when evnx is built with the migrate feature:
cargo install evnx --features migrate
What you need
- ›A GitHub Personal Access Token (PAT) with the
secrets:writepermission for the target repository - ›The repository in
owner/repoformat - ›A
.envfile with the secrets you want to upload
The token can be supplied three ways (in priority order):
- ›
--github-token "ghp_…"flag - ›
GITHUB_TOKENenvironment variable - ›Interactive prompt (evnx asks if neither of the above is set)
How it works
evnx does not handle raw secret values. Before uploading, it:
- ›Fetches the repository's RSA public key from
GET /repos/{owner}/{repo}/actions/secrets/public-key - ›Encrypts each value using a libsodium sealed box (X25519 / XSalsa20-Poly1305) with that public key
- ›Sends the encrypted payload to
PUT /repos/{owner}/{repo}/actions/secrets/{NAME}
Only the encrypted ciphertext leaves your machine. GitHub decrypts it server-side when the secret is used in a workflow.
Basic usage
evnx migrate \
--from env-file \
--to github-actions \
--repo owner/repo \
--github-token "$GITHUB_TOKEN"Without --from, evnx defaults to env-file and reads .env in the current directory. Without --source-file, it reads .env.
Step-by-step walkthrough
Scan and validate your .env first
Avoid uploading test keys or placeholder values to GitHub.
evnx scan
evnx validate --strictPreview the migration
Run with --dry-run to see exactly which secrets would be uploaded, skipped, or would conflict with existing ones — without making any API calls.
evnx migrate \
--to github-actions \
--repo owner/repo \
--github-token "$GITHUB_TOKEN" \
--dry-runExample output:
✓ Loaded 14 secrets from env-file
Migration plan:
• 11 to upload
• 2 to skip (already exist)
• 1 conflicts (will overwrite)
ℹ️ Dry-run — no changes made.
Filter to what you need
Your .env probably has local-only variables that should not go into CI. Use --exclude to drop them.
evnx migrate \
--to github-actions \
--repo owner/repo \
--exclude "*_LOCAL,*_DEV,DATABASE_URL_TEST" \
--dry-runRun the migration
Remove --dry-run. evnx shows a progress bar and prints a line per secret.
evnx migrate \
--to github-actions \
--repo owner/repo \
--github-token "$GITHUB_TOKEN" \
--exclude "*_LOCAL,*_DEV"🚀 Migrating to GitHub Actions Secrets…
Migration plan:
• 11 to upload
Proceed with migration? › Yes
⠸ [████████████████████░░░░░░░░░░░░░░░░░░░░] 10/11 DB_PASSWORD
✓ DATABASE_URL
✓ DB_PASSWORD
✓ STRIPE_SECRET_KEY
…
Summary:
✓ 11 uploaded
⊘ 2 skipped
Update your workflows
Reference the uploaded secrets in .github/workflows/*.yml:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run app
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
run: ./deploy.shHandling conflicts
When a secret already exists in the repository, evnx gives you three options:
Interactive (default) — prompts for each conflict:
evnx migrate --to github-actions --repo owner/repo
# → Overwrite existing secret 'DATABASE_URL'? › NoSkip all existing — never overwrite:
evnx migrate --to github-actions --repo owner/repo --skip-existingOverwrite all existing — no prompts:
evnx migrate --to github-actions --repo owner/repo --overwriteRenaming keys before upload
Strip an application prefix from key names so GitHub secrets stay clean:
# APP_DATABASE_URL → DATABASE_URL
evnx migrate \
--to github-actions \
--repo owner/repo \
--strip-prefix "APP_"Add an environment prefix so staging and production secrets can coexist in one repository:
# DATABASE_URL → PROD_DATABASE_URL
evnx migrate \
--to github-actions \
--repo owner/repo \
--add-prefix "PROD_"Token permission requirements
The PAT must have:
| Permission | Scope |
|---|---|
| Repository secrets — read | Required to check for existing secrets |
| Repository secrets — write | Required to create or update secrets |
For a fine-grained PAT: Repository permissions → Secrets → Read and write.
For a classic PAT: the repo scope covers both.
Rate limiting
GitHub allows 1 000 API requests per hour for authenticated users. evnx adds a 100 ms delay between secret uploads to stay comfortably within this limit for repositories with up to a few hundred secrets.
Next steps
- ›Migration concepts — understand filtering and transforms
- ›AWS Secrets Manager migration — if you also use AWS
- ›
evnx scan— detect real secrets before migrating - ›
evnx backup— encrypt your local.envafter a successful migration