
September 11, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
You cannot ship Laravel, WordPress, or custom APIs on a schedule if credentials live in Git. To manage secrets safely in pipelines, you must treat every CI/CD job as a hostile environment where logs, caches, and forked pull requests can expose keys. On real client projects I maintain with GitLab CI and Deployer 7, one leaked APP_KEY or database password costs hours of rotation and downtime. This guide walks through the patterns that actually work in 2026 production pipelines.
How do you manage secrets safely in pipelines without leaking credentials?
Start with a simple rule: no secret ever belongs in source control. That includes .env files, Terraform tfvars with passwords, and base64 strings that look encoded but are trivially decoded. A common mistake is committing a sample .env.example with real-looking values copied from staging.
Your pipeline should receive secrets from one of three trusted sources:
- Native CI/CD secret variables (GitLab CI/CD variables, Jenkins credentials, GitHub Actions secrets)
- A dedicated secret manager (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault)
- Short-lived tokens fetched at deploy time via OIDC or workload identity
Each job gets only the secrets it needs. A lint job should not see production database credentials. A deploy job should not retain SSH keys after the step finishes. Principle of least privilege is not optional here.
Before you wire anything, run a baseline scan. Tools like Gitleaks catch keys already sitting in history. I have seen teams discover Stripe test keys from three years ago during a routine audit. Pair scanning with pre-commit hooks so new leaks never merge. Read the full workflow in the guide on secrets scanning in Git and CI with Gitleaks.
Define a secrets classification policy
Not every value is equal. Group credentials by blast radius:
- Tier 1 — Critical: production database passwords, payment gateway secrets, SSH deploy keys, cloud root tokens
- Tier 2 — Sensitive: staging credentials, API keys with write scope, OAuth client secrets
- Tier 3 — Low risk: public API endpoints, read-only analytics IDs, feature flags
Tier 1 secrets require masked variables, protected branches, manual approval gates, and quarterly rotation. Tier 3 can live in plain CI variables if they are genuinely public. Document the tiers in your runbook so new developers do not guess.
What is the safest way to store CI/CD pipeline secrets?
Native CI variables work well for small teams on a single platform. GitLab masked and protected variables, combined with environment scopes, cover most Laravel deploy pipelines I run on Ubuntu servers. When you outgrow that — multiple clouds, Kubernetes, dozens of microservices — move to a central secret manager.
The comparison below reflects what I see on production systems in 2026. Your choice depends on team size, cloud vendor, and whether you need dynamic database credentials.
| Approach | Best for | Rotation | Audit trail | Complexity |
|---|---|---|---|---|
| CI native variables | Single-repo Laravel/WordPress deploys | Manual | Platform logs | Low |
| HashiCorp Vault | Multi-env, dynamic DB creds | Automatic TTL | Full request log | High |
| Cloud secret managers | AWS/GCP/Azure-heavy stacks | Scheduled + Lambda | CloudTrail / equivalent | Medium |
| Sealed Secrets / ESO | GitOps on Kubernetes | Re-encrypt on rotate | K8s audit + Vault | Medium |
| Ansible Vault | Config management playbooks | Manual re-encrypt | Git history only | Low–medium |
For sister sites I deploy with Deployer 7 on shared EC2, GitLab CI variables scoped to production are enough. The shared .env on the server never enters Git. The pipeline writes nothing sensitive into the release artefact. See handling secrets in CI/CD pipelines safely for the companion patterns.
If you run Kubernetes, never treat default Secrets as encryption. They are base64-encoded ConfigMaps, not vaults. Use Kubernetes secrets management done right or the External Secrets Operator with Vault behind it.
How do you inject secrets into GitLab CI and Jenkins pipelines?
Injection timing matters. Fetch secrets as late as possible. Export them into the job environment. Use them. Unset them before the job ends if your runner supports it.
GitLab CI example for a Laravel 12 deploy
This pattern matches production pipelines on PHP 8.3+ with Composer 2.10. Secrets arrive from GitLab variables. The deploy step never prints them.
# .gitlab-ci.yml
stages:
- test
- deploy
variables:
COMPOSER_ALLOW_SUPERUSER: "1"
test:
stage: test
image: php:8.3-cli
script:
- composer install --no-interaction
- cp .env.testing .env
- php artisan test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
deploy_production:
stage: deploy
environment:
name: production
url: https://example.com
script:
- composer install --no-dev --optimize-autoloader
- vendor/bin/dep deploy production -o branch=$CI_COMMIT_SHA
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
only:
variables:
- $CI_DEPLOY_FREEZE == null
Configure these variables in GitLab under Settings → CI/CD → Variables:
SSH_PRIVATE_KEY— type File, masked, protected, scope: productionDB_PASSWORD— masked, protected, never passed to test jobsAPP_KEY— lives on the server in shared.env, not in the pipeline at all
Deployer reads SSH from the file variable. Database credentials stay on the server. The pipeline only needs deploy access. That split reduces exposure if a job log is ever misconfigured.
Jenkins declarative pipeline pattern
Jenkins stores credentials in its credential store. Bind them by ID. Never interpolate secrets into shell echo commands.
pipeline {
agent any
environment {
DEPLOY_HOST = 'production.example.com'
}
stages {
stage('Deploy') {
steps {
withCredentials([
sshUserPrivateKey(
credentialsId: 'prod-deploy-key',
keyFileVariable: 'SSH_KEY_FILE'
)
]) {
sh '''
export GIT_SSH_COMMAND="ssh -i $SSH_KEY_FILE -o StrictHostKeyChecking=yes"
vendor/bin/dep deploy production
'''
}
}
}
}
}
For deeper Jenkins setup, see the Jenkins declarative pipeline tutorial. The same least-privilege rules apply regardless of platform.
Protect pull request pipelines from secret theft
Forked merge requests are a classic attack vector. A contributor opens a PR. Your pipeline runs their code with access to CI variables. Their script exfiltrates secrets to an external URL.
Fix this with platform controls:
- Mark all production variables as protected (GitLab) or limit to default branch (GitHub Actions)
- Require manual approval for external contributor pipelines
- Never pass Tier 1 secrets to MR pipelines — use ephemeral test credentials only
- Disable pipeline secrets entirely for fork PRs when possible
The official GitLab documentation on CI/CD variables explains masked variable limits. Masking hides values in logs. It does not stop a malicious script from reading $DB_PASSWORD and sending it outbound. Branch protection and MR approval are your real defence.
Which secret management tools work best with deployment pipelines?
Tool choice follows architecture, not hype. A WooCommerce shop on managed hosting needs different controls than a Laravel API with twelve microservices.
HashiCorp Vault for dynamic credentials
Vault shines when credentials should expire automatically. A pipeline requests a database username valid for one hour. Vault creates it. The app connects. Vault revokes it after TTL. No long-lived password sits in a variable forever.
Pair Vault with the External Secrets Operator on Kubernetes, or call the Vault API from a CI script before deploy. See Vault dynamic secrets for databases and External Secrets Operator with Vault for implementation detail.
Cloud-native managers for AWS and Azure stacks
If your pipeline already runs on AWS CodePipeline or Azure DevOps, use the native store. Azure Pipelines can pull from Key Vault with a service connection. No duplicate secret copy means fewer rotation points. The guide on using Azure Key Vault secrets in pipelines covers YAML binding.
Ansible Vault for infrastructure playbooks
Teams that provision servers with Ansible often encrypt sensitive vars with Ansible Vault. The vault password itself must live in CI — usually as a single protected variable. Encrypt group_vars. Decrypt at runtime. Never commit the vault password. Details are in Ansible Vault encrypt secrets in playbooks.
On legal-tech portals and booking systems I have shipped, the winning pattern is boring. GitLab variables for deploy keys. Shared .env on the server. Redis and MySQL credentials never touch the build artefact. Projects like Notary Kathmandu and Court Marriage In Nepal follow that same Deployer pipeline on shared infrastructure.
How do you audit, rotate, and recover pipeline secrets?
Storing secrets safely is half the job. Rotation and audit complete the loop. OWASP treats insufficient logging and monitoring as a top application risk. Secret access belongs in that log stream.
Rotation schedule that teams actually follow
Ambitious weekly rotation fails on small teams. Use tiered schedules instead:
- Quarterly: production database passwords, SSH deploy keys, payment gateway API secrets
- On staff departure: every credential the person could have seen — assume clipboard history
- On suspected leak: immediate rotation plus Git history scan plus pipeline log review
- On dependency breach: rotate third-party tokens for affected integrations
Document rotation in a runbook stored outside the repo. A shared password manager or internal wiki works. Use the password generator for strong random values. Never reuse passwords across staging and production.
Audit checklist after every deploy pipeline change
- Confirm no new plaintext secrets appeared in Git (
gitleaks detect --source .) - Verify protected branch rules still block unapproved MR pipelines from prod variables
- Check CI job logs for accidental echo of environment variables
- Review who has Maintainer access on the GitLab project or Jenkins admin role
- Confirm server-side
.envpermissions are640owned by deploy user, not world-readable
On Ubuntu servers I administer, wrong ownership on storage/ or .env is a recurring post-deploy issue. The app works. The secret file is readable by other users on shared hosting. Fix permissions in the same pipeline step that symlinks the release.
Rollback without re-exposing secrets
Failed deploys should not force you to paste credentials into a terminal under pressure. Deployer rollback via dep rollback swaps the symlink to the previous release. No new secrets needed. The shared .env persists across releases. Read how to roll back a failed deployment safely for the full sequence.
If a secret was exposed during the failed deploy, rollback the code first. Rotate the secret second. Order matters. Rolling back does not undo a leak that already happened.
API and payment secrets in Laravel pipelines
Laravel apps often integrate eSewa, Khalti, Stripe, or SMS gateways. Those secrets belong in server .env, not in CI variables, unless the pipeline itself calls the API during build. For API development projects, I inject third-party keys only into the runtime environment on the app server. CI sees deploy credentials. The app container sees payment credentials. Two separate trust boundaries.
When building containers, never bake secrets into image layers. Docker build args end up in image history. Pass runtime env vars at deploy. If you use Docker Swarm or Compose secrets, follow the patterns in Docker secrets in Compose and Swarm.
Multi-cloud and IaC considerations
Terraform state files can contain plaintext secrets if you are careless with outputs. Store state remotely with encryption. Use Terraform state management best practices. Scan plans in CI with tfsec before apply. The tfsec in your pipeline guide shows the GitLab job setup.
For teams spanning AWS and Azure, centralise with Vault or a cloud-agnostic manager. The multi-cloud secrets management article compares trade-offs. Avoid copying the same password into three different stores. One source of truth. Many consumers.
Key Takeaways
- Never commit secrets to Git — scan history with Gitleaks and block leaks in pre-commit hooks before they reach CI.
- Scope CI variables to protected branches and deploy stages only; fork PRs must not receive production credentials.
- Keep long-lived app secrets in server-side
.env; give the pipeline only the deploy key it needs. - Rotate Tier 1 credentials quarterly and immediately after staff changes or suspected exposure.
- Choose native CI variables for small Laravel deploys; adopt Vault or cloud managers when service count grows.
- Audit pipeline logs, file permissions, and Maintainer access after every change to deploy configuration.
People Also Ask
Can masked CI variables still leak secrets?
Yes. Masking hides values in job logs. It does not stop a malicious script inside the job from reading the variable and sending it to an external server. Combine masking with protected branches, MR approval for forks, and least-privilege scoping so untrusted code never runs with production variables.
Should APP_KEY and database passwords live in the CI pipeline?
Usually no. On Deployer-style Laravel deploys, APP_KEY and database credentials belong in a shared server .env outside the release directory. The pipeline needs an SSH key to deploy code. The running app reads its own secrets from disk. That separation limits blast radius if a CI job is compromised.
How often should pipeline secrets be rotated?
Rotate production database passwords and deploy keys at least quarterly. Rotate immediately when a team member with access leaves or when scanning detects a leak. Payment gateway and OAuth secrets follow the same rule. Document dates in a runbook so rotation does not depend on one person's memory.
What is the cheapest way to manage secrets safely in pipelines?
GitLab or GitHub native secret variables cost nothing beyond your existing CI plan. Add Gitleaks scanning in the pipeline — also free and open source. For Nepal SMB projects on a single VPS, that stack plus a properly permissioned server .env covers the basics without Vault licensing or extra infrastructure at Rs 5,000–15,000/month (~USD 37–110).
Build Pipelines That Keep Credentials Out of Harm's Way
To manage secrets safely in pipelines, treat credential hygiene as part of deployment design — not a security afterthought. Keep secrets out of Git. Inject them at runtime with least privilege. Scan, rotate, and audit on a schedule your team can sustain. The patterns here match what I use on production Laravel and legal-tech systems deployed through GitLab CI and Deployer 7.
If your current pipeline still passes database passwords through build logs or shares one SSH key across staging and production, fix that before the next feature ship. For hands-on help hardening CI/CD, server access, or Linux system administration, review the support and maintenance services or explore related guides on pipeline automation best practices and GitOps sealed secrets. Need a full audit of your deploy workflow? Contact us to walk through your setup.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

