Skip to Content

Managing Environment Variables and Secrets in DevOps

Managing Environment Variables and Secrets in DevOps
Managing Environment Variables and Secrets in DevOps

Config and secrets are not the same thing, and treating them as the same thing is how credentials end up in public repositories.

Highlights

  • Managing environment variables and secrets well starts with a distinction most teams skip, because configuration and secrets need different storage, different access controls, and different lifecycles.
  • GitGuardian counted 28.65 million new hardcoded secrets in public GitHub commits during 2025, up 34 percent on the previous twelve months and the sharpest annual increase it has ever recorded.
  • Internal repositories are roughly six times more likely to contain a hardcoded secret than public ones, which makes the private repository a false sense of safety rather than a control.
  • Environment variables leak through at least six quiet paths that have nothing to do with committing a file, including process listings, crash reports, and container inspection.
  • Kubernetes Secrets are base64 encoded rather than encrypted, so anyone who can read the object can read the value unless you have taken additional steps.
  • Terraform state files store secret values in plain text, which turns a state bucket into one of the most sensitive stores your team owns.
  • Rotation is where secret programmes quietly fail, since GitGuardian found that 64 percent of valid secrets leaked in 2022 were still not revoked in 2026.

In 2025, GitGuardian detected 28.65 million new hardcoded secrets in public GitHub commits. That is a 34 percent rise on the previous twelve months, and the steepest one year climb across five editions of the company's annual report. The more uncomfortable number is what happened next, because 64 percent of the valid secrets leaked back in 2022 were still not revoked when the 2026 report was published. The leak is not really the incident. The credential that nobody ever turned off is the incident, and it stays an incident for years.

Two more findings from that research reframe how most teams think about this. Internal repositories are around six times more likely to contain a hardcoded secret than public ones, at roughly 32 percent against 6 percent, which means the private repository your team treats as safe is statistically the more dangerous place. And roughly 28 percent of incidents began somewhere other than a repository altogether, inside collaboration tools such as Slack, Jira, and Confluence, where a credential pasted into a thread to unblock a colleague lives on indefinitely.

This guide is about closing those gaps at a practical level. It starts with a distinction that decides everything, walks through the surprisingly many ways an environment variable escapes a running process, then covers what to actually do in local development, Docker, Kubernetes, CI/CD, and Terraform, before finishing with the two things teams skip most: rotation, and the first hour after a leak.

What we mean by environment variables and secrets

Managing environment variables and secrets is the practice of separating an application's configuration from its code, storing the sensitive parts in a system designed to protect them, delivering both to the right process at the right moment, and controlling who can read them, how long they live, and how quickly they can be replaced.

The phrase is usually said as though it describes one activity. It describes two, and the difference between them is the foundation everything else rests on.

Configuration and secrets are not the same thing

Here is the distinction that quietly determines whether the rest of your practice works.

Configuration is anything that changes between environments but causes no harm if somebody reads it. A log level, a feature flag, a region name, a public API endpoint, a connection pool size. If this leaked, you would shrug.

A secret is anything that grants access. A database password, an API key, a signing key, a token, a certificate private key. If this leaked, you would need to act, and the action would be revocation rather than embarrassment.

PropertyConfigurationSecrets
Harm if disclosed None in practice. Direct, since disclosure grants access.
Where it can live Repository, config map, environment. A secrets manager, encrypted at rest with access control.
Who may read it Broadly, including the whole engineering team. Narrowly, and every read should be auditable.
Change frequency Whenever behaviour changes. On a schedule, and immediately after suspicion.
Version control Committed happily. Never committed, even encrypted, without deliberate design.
Detection needed None. Scanning, alerting, and a revocation runbook.

The reason this matters so much in practice is that most teams pick one delivery mechanism, usually environment variables, and push everything through it. That works technically, and then the secret inherits the security posture of the configuration, which is to say almost none.

Did you know

The habit of putting everything into environment variables traces back to the Twelve Factor App methodology published in 2011, which recommended storing config in the environment specifically to keep it out of code. It was excellent advice for its time and it solved a real problem, which was credentials hardcoded in source files. What it did not anticipate was containers, orchestrators, crash reporting services, and CI systems, all of which read and forward environment variables in ways nobody was thinking about in 2011. The principle of separating config from code remains right. The assumption that the environment is a private place has not aged as well.

The six ways an environment variable leaks

This is the section worth reading twice, because none of these involve committing a file to a repository and most engineers have not thought about several of them.

The first path is the process listing. On Linux, a process's environment is readable at /proc/<pid>/environ. The kernel restricts this to the same user or root, but that is a lower bar than people assume in a container running everything as one user, or on a host where a support engineer has sudo. Anyone who can run commands in that context can read every secret in every process's environment.

The second path is child processes. Environment variables are inherited by default, so every subprocess your application spawns receives the whole environment. A build script that shells out, an application that invokes a helper binary, a debugging tool someone runs inside the container, all of them get the database password whether or not they need it.

The third path is crash reporting and error tracking. Many error handlers capture the environment as context to help with debugging, which is genuinely useful right up until the moment a stack trace containing your production credentials is uploaded to a third party service and sits in a searchable dashboard that dozens of people can access.

The fourth path is container inspection and image layers. Running docker inspect on a container prints its environment variables in full, and anyone with access to the Docker socket can do this. Worse, if a secret was passed as a build argument or set with an ENV instruction in a Dockerfile, it is baked into the image layers and travels with that image to every registry and every machine that pulls it.

The fifth path is CI/CD logs. Build systems echo commands, print environment dumps when debugging is enabled, and store logs for a long time. A single env or printenv added during troubleshooting can write your entire secret set into a log that is retained for a year and readable by everyone with repository access.

The sixth path is developer machines and collaboration tools. A .env file copied to a laptop for local testing, a credential pasted into a Slack thread to unblock a teammate, a screenshot in a Jira ticket. GitGuardian's finding that roughly 28 percent of incidents begin somewhere other than a repository is describing exactly this, and none of it is caught by scanning your code.

In the wild

A team investigating a routine application crash discovered that their error tracking dashboard had been storing full environment dumps for eight months, including the production database password, the payment provider key, and a cloud access key. Nothing had been committed to a repository. No scanner had fired. Their secret scanning was clean because the secrets were never in the code, and the leak path was an error handler doing precisely what it was configured to do. The fix took ten minutes, which was adding a denylist to the error reporter, and the rotation of everything exposed took two days.

The maturity ladder

Most teams sit somewhere on this ladder without having consciously chosen a rung. Finding yours is a useful five minutes.

LevelWhat it looks likeMain weakness
0. Hardcoded Credentials written directly into source files. Anyone with repository access has production access, forever.
1. Environment file A .env file on disk, listed in .gitignore. One careless git add -f or a copied laptop and it is gone.
2. Encrypted in repository Values encrypted with SOPS or Sealed Secrets, committed safely. Key management becomes the new problem, and rotation is manual.
3. Secrets manager Vault or a cloud secret manager, fetched at runtime. Requires identity for machines, and adds a runtime dependency.
4. Dynamic secrets Short lived credentials generated per request or per session. The most work to set up, and the least to worry about afterwards.

Two observations about this ladder are worth more than the ladder itself.

Level 1 is where most teams live and it is not shameful, because it is a genuine improvement over level 0 and it works fine for local development. The mistake is staying at level 1 for production, where the blast radius of a copied file is entirely different.

Reaching level 4 sounds exotic and is more achievable than it looks, because the tooling has matured considerably. A dynamic secret is one that does not exist until something asks for it, lives for minutes or hours, and expires automatically. Vault's database secrets engine will create a database user on demand with a short lease, and cloud providers do something similar when a workload assumes a role instead of holding a key. A credential that expires in an hour is a credential that mostly cannot be leaked in a way that matters, which sidesteps the rotation problem rather than solving it.

Local development, done properly

Start here, because habits formed locally propagate everywhere.

Use a .env file for local configuration, keep it out of version control, and commit a template alongside it so that a new joiner knows what they need without being handed anything sensitive.

cat > .env.example <<'TEMPLATE'
DATABASE_URL=postgres://user:password@localhost:5432/appdb
STRIPE_API_KEY=
LOG_LEVEL=debug
FEATURE_NEW_CHECKOUT=false
TEMPLATE

printf '.env\n.env.local\n*.pem\n*.key\n' >> .gitignore
git check-ignore -v .env

The first command writes a template containing the variable names with placeholder or empty values, which documents the shape of the configuration without exposing anything real. The second appends the sensitive filename patterns to .gitignore, including private key extensions that are easy to forget. The third command is the one people skip, and it verifies that .gitignore actually matches the file, printing the rule responsible, because assuming a pattern works and confirming it works are different things.

Three habits go with this. Never put real production credentials in a local .env file, since local machines are backed up, synced, and occasionally lost, and a development database with fake data is almost always sufficient. Install a pre commit secret scanner so that a mistake is caught before it becomes a commit rather than after it becomes history. And when you do need a shared local secret, fetch it from your secrets manager with a short lived token rather than passing files around.

Quick tip

If a secret does reach a commit, deleting it in a later commit does not remove it, because Git keeps the full history and the value remains reachable in the earlier object. Rewriting history is possible with tools built for the job, and it is not the first thing to do. Revoke the credential first, because rotation is what actually ends the exposure, and history rewriting is cleanup afterwards. Assume that anything pushed to a shared remote, even briefly, has been seen. Automated scanners watch public GitHub commits continuously and credentials have been observed being used within minutes of exposure.

Containers, where secrets get baked in

Docker introduces a failure mode that catches almost everyone once, which is the difference between build time and run time.

Anything you pass with ARG or set with ENV in a Dockerfile becomes part of the image. Image layers are immutable and shareable, so a secret set during a build travels to your registry and to every host that pulls the image, and deleting the file in a later layer does not remove it from the earlier one. The layer is still there.

FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci --omit=dev
COPY src ./src
USER node
CMD ["node", "src/server.js"]

That Dockerfile solves the common case of needing a private registry credential during a build. The --mount=type=secret syntax, provided by BuildKit, makes the credential available as a file only for the duration of that single RUN instruction, and it is never written into any layer. You pass the value at build time with a flag pointing at a local file, and the resulting image contains no trace of it.

At run time the picture is different, because the container genuinely needs its configuration. Passing values with the -e flag or an env file works and is the normal approach, and the important habit is knowing that docker inspect will display all of them to anyone with access to the daemon. For anything highly sensitive, mounting a file rather than setting a variable is meaningfully safer, since a mounted file can have restrictive permissions and does not appear in inspection output or get inherited by every child process.

Watch out

A secret that appears in an image layer is not removed by deleting the file in a later instruction, and it is not removed by overwriting the variable. Layers are additive and each one is retained. If you discover a credential was baked into an image, the response is to rotate the credential and rebuild, not to patch the image, because every copy of that image already pulled to every host still contains the original value.

Kubernetes, where base64 is not encryption

Kubernetes has a purpose built object for this and a widespread misunderstanding about what it provides.

A Secret object stores data base64 encoded. Base64 is an encoding, not encryption, and decoding it requires no key and no privilege. Anyone who can read the Secret object can read the value with a single command, and by default Secrets are also stored unencrypted in etcd, meaning anyone with access to the etcd data or a backup of it has everything.

kubectl create secret generic app-db \
  --from-literal=DATABASE_PASSWORD='s3cr3t-value' \
  --namespace prod

kubectl get secret app-db -n prod -o jsonpath='{.data.DATABASE_PASSWORD}' | base64 -d

kubectl auth can-i get secrets --namespace prod --as system:serviceaccount:prod:web

The first command creates a Secret from a literal value, which is fine for a demonstration though in practice you would source it from a file or a manager. The second retrieves the stored value and decodes it, and running this once is worth more than any amount of explanation, because seeing your password print to the terminal in one line makes the point permanently. The third command is the one to build a habit around, since it asks whether a specific ServiceAccount can read Secrets in a namespace, and the answer tells you whether your access controls are what you believe them to be.

Four things turn Kubernetes Secrets from a filing convention into an actual control.

Enable encryption at rest in the API server configuration so that Secret data is encrypted in etcd, ideally with keys held in a cloud key management service rather than a local file.

Restrict RBAC properly, because the default of many roles being able to list Secrets across a namespace defeats everything else. A workload should be able to read the specific Secrets it needs and nothing else.

Prefer mounted volumes over environment variables when delivering secrets to pods. A mounted Secret appears as a file with controlled permissions, it is not visible in docker inspect or a process environment, it is not inherited by child processes, and it updates automatically when the Secret changes, whereas an environment variable is fixed for the life of the pod.

Bring secrets in from a real manager rather than storing them in the cluster as the source of truth. The External Secrets Operator is the common pattern here, syncing values from Vault or a cloud secret manager into Kubernetes Secrets so the manager remains authoritative and the cluster holds a cache.

For teams doing GitOps, where everything must live in Git, two tools solve the encrypted commit problem. Sealed Secrets encrypts a value so that only the controller running in your specific cluster can decrypt it, making the encrypted form safe to commit. SOPS encrypts values inside otherwise readable YAML using a key from a cloud key management service, so the file structure stays reviewable in a pull request while the values stay protected.

Build the secrets management foundation properly

Vault is the tool most teams eventually reach for, and it rewards structured learning because its concepts, including auth methods, secrets engines, leases, and policies, are genuinely different from a simple key value store. The HashiCorp Certified Vault Associate course on KodeKloud covers Vault fundamentals, authentication methods, dynamic secrets engines, access policies, and production deployment, which maps directly onto levels three and four of the maturity ladder above.

Certification prep

HashiCorp Certified Vault Associate

Vault fundamentals, authentication methods, dynamic secrets engines, and access policies. Levels three and four of the maturity ladder, properly taught.

VaultDevOpsCloud
Explore the Vault course →

CI/CD, the highest value target

Your pipeline holds credentials for everything it can deploy to, which makes it the single most attractive place in your infrastructure. It is also where the most impactful modern improvement is available.

The traditional approach stores long lived cloud access keys as CI variables. Those keys do not expire, they are readable by anything that can run a job, and they are copied into every build environment. The better approach is workload identity federation using OpenID Connect, where your CI provider proves the identity of a specific job to your cloud provider, which returns a short lived credential scoped to that job. No long lived key exists anywhere, so there is nothing to leak and nothing to rotate.

name: deploy
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/deploy-production
          aws-region: eu-west-1
      - run: ./scripts/deploy.sh

Several details in that workflow are doing security work. The id-token: write permission is what allows the job to request an identity token, and without it the federation cannot happen. The contents: read line follows least privilege by granting only what is needed rather than accepting broad defaults. Naming an environment lets you attach protection rules such as required reviewers, so a production deployment can require human approval. And the credentials step exchanges the job's identity token for a temporary role session rather than reading a stored key, meaning there is no secret in this repository at all.

Three further habits matter in pipelines. Scope credentials to environments so that a pull request build cannot touch production. Rely on your platform's masking to redact secret values from logs, while remembering that masking is best effort and a value that has been transformed, encoded, or split across lines can slip through. And avoid passing secrets as command line arguments, since arguments are visible in process listings, whereas environment variables and files are marginally better.

In the wild

Analysing a large supply chain attack, GitGuardian found that 59 percent of the machines caught up in it were build runners rather than individual developer laptops. That ratio is worth sitting with, because it says the shared build infrastructure holds more credentials than any individual laptop, and it is a single target that reaches many systems. Treating a CI runner as a production system with production level access controls is a more accurate mental model than treating it as a build utility.

Terraform, and the state file problem

This one surprises people and deserves its own section because the consequence is severe and the cause is not obvious.

Terraform stores the full state of your infrastructure in a state file, and that state includes the values of resources it created. If Terraform creates a database with a password, that password is in the state file in plain text. Marking a variable as sensitive prevents it appearing in console output, and it does not encrypt anything in the state.

The practical consequences follow directly. Your state backend is as sensitive as the most sensitive credential in your infrastructure, so it needs encryption at rest, strict access control, versioning, and audit logging, and it should never sit in a repository or an unrestricted bucket.

A better pattern avoids the problem where possible. Rather than having Terraform generate and store a password, have it create the resource and let a secrets manager own the credential, or use dynamic credentials so no long lived password exists to store. Where Terraform must handle a secret, read it from a secrets manager at plan time using a data source rather than passing it as a variable, so the value is fetched rather than persisted in your variable definitions.

Watch out

Marking a Terraform variable as sensitive only suppresses it from plan and apply output. The value is still written to the state file in plain text, and anyone who can read the state can read the secret. Treat your state backend as a secrets store, because functionally that is what it has become.

Rotation, where programmes quietly fail

Everything above is about preventing exposure. This section is about limiting how long an exposure matters, and it is the part almost everyone postpones.

The GitGuardian figure that 64 percent of valid secrets leaked in 2022 remained unrevoked in 2026 is not really a story about detection. Those organisations mostly knew. What they lacked was a repeatable path from knowing to revoking, because rotating a credential in a real system means finding every consumer, updating them all, and confirming nothing broke, and nobody wants to do that at 4pm on a Friday.

Three practices make rotation survivable.

Design for two valid credentials at once. If your system can accept both an old and a new credential during a transition window, rotation stops being a synchronised cutover and becomes a rolling change. You add the new one, migrate consumers gradually, confirm the old one is unused through access logs, then revoke it. Most cloud providers support two active access keys per identity specifically to enable this pattern, and many APIs support multiple valid keys for the same reason.

Keep an inventory of what uses what. The reason rotation is frightening is uncertainty about what will break, and that uncertainty is a documentation problem rather than a technical one. A simple record of every secret, its owner, its consumers, and its last rotation date converts a scary operation into a checklist.

Rotate on a schedule before you need to. A team that has rotated a credential deliberately in a quiet week can rotate it under pressure. A team whose first rotation happens during an incident is learning the procedure at the worst possible moment. This is exactly the same argument as practising a restore from backup rather than trusting that backups exist.

Dynamic secrets are the structural answer to all of this, and it is worth naming plainly. A credential that expires in an hour does not need a rotation procedure, because expiry is rotation, automatically and continuously. The work moves from operating a rotation process to setting up dynamic issuance once.

The first hour after a leak

Assume this will happen, because at the volumes in the opening statistics it happens to competent teams regularly. Having a sequence written down beforehand is the difference between a contained incident and a slow one.

Revoke first, investigate second. The instinct is to work out how it happened, and that instinct costs you time during which the credential still works. Disable or rotate the credential immediately, then investigate with the pressure off. A revoked credential cannot be used regardless of who saw it.

Assume it was used. Credentials exposed publicly have been observed being picked up within minutes by automated scanners. Check access logs for the credential from the moment of exposure, looking for unfamiliar addresses, unusual regions, and unexpected API calls.

Find every copy. The same value is probably in more places than the one you found. Check other repositories, CI variables, container images, configuration management, wikis, and chat history, since the collaboration tool paths are exactly the ones scanners miss.

Rewrite history if appropriate, but not first. Removing the value from Git history is worth doing for hygiene, and it does nothing about exposure since the credential has already been seen. Rotation is the control, and history rewriting is cleanup.

Write down what the leak path was. The value of an incident is the change it produces. A secret in a commit means you need pre commit scanning. A secret in a log means you need output masking. A secret in an error report means you need a denylist in the error handler. Each path has a different fix and treating them all as carelessness fixes none of them.

In the wild

A developer pushed a cloud access key to a public repository and noticed within about ten minutes. By the time they had opened a support ticket, the key had already been used from an unfamiliar region to launch compute instances. The pattern is common enough to be predictable, because automated scrapers monitor public commits continuously and mining workloads are the usual payload. The team's postmortem changed one thing that mattered, which was making revocation the documented first step rather than something they got to after understanding what happened.

Choosing a tool

There is no single right answer, and the honest comparison is about what each option costs you to operate.

ApproachFitsCost to run
.env files with gitignore Local development only. Nothing, and it provides no protection beyond hiding the file.
SOPS GitOps teams wanting readable, reviewable encrypted files. Key management, and manual rotation.
Sealed Secrets Kubernetes GitOps where encrypted values must live in Git. Cluster specific keys, and a controller to operate.
Cloud secret managers Teams already committed to one cloud. Low, since it is managed, though cross cloud gets awkward.
HashiCorp Vault Teams needing dynamic secrets, multiple clouds, or fine grained policy. Highest, as it is real infrastructure to run and secure.
External Secrets Operator Kubernetes clusters syncing from an external manager. Modest, and it pairs with one of the managers above.

A reasonable progression for most teams is to keep .env files strictly for local development, adopt the secret manager of whichever cloud you already use for production, add the External Secrets Operator if you run Kubernetes, and consider Vault when you genuinely need dynamic secrets or a single system spanning multiple clouds. Skipping to Vault before you have the operational capacity to run it well is a common and expensive detour.

Practise this on a real cluster

The difference between reading that Kubernetes Secrets are base64 encoded and decoding one yourself in a terminal is the difference between knowing and understanding. The Certified Kubernetes Security Specialist (CKS) course on KodeKloud covers secrets handling, RBAC, and cluster hardening in depth, and the KodeKloud playgrounds give you clusters where you can create, decode, mount, and lock down Secrets without touching anything that matters.

Certification prep

Certified Kubernetes Security Specialist (CKS)

Cluster secrets handling, RBAC, and hardening in depth. The difference between knowing Secrets are base64 and understanding what to do about it.

KubernetesSecurityDevOps
Explore the CKS course →

Where to start

  1. Write down every secret your team uses, where it lives, who can read it, and when it was last rotated, because the inventory will be shorter and more alarming than expected.
  2. Separate configuration from secrets in one service, moving the harmless values somewhere convenient and the sensitive ones somewhere controlled.
  3. Turn on secret scanning for your repositories including the private ones, since those are statistically the more likely to contain something.
  4. Add a pre commit scanner to one repository so mistakes are caught before they become history.
  5. Replace one long lived CI credential with OpenID Connect federation, because that single change removes a permanent key from existence.
  6. In a cluster, decode a Secret with base64 -d in front of your team, then check whether encryption at rest is enabled and who can list Secrets.
  7. Rotate one credential deliberately this quarter while nothing is on fire, and write down the procedure while you do it.

Conclusion

Those opening statistics describe a problem growing faster than the developer population, and the reason is not carelessness. It is that secrets are easy to create, easy to copy, and structurally hard to track, so every new service, integration, and automation adds credentials faster than anyone retires them.

What actually helps is unglamorous and specific. Separate configuration from secrets so each gets appropriate handling. Know the leak paths that have nothing to do with committing a file, because those are the ones your scanning will never catch. Use the right mechanism for each stage, meaning environment files locally, mounted secrets in clusters, federated identity in pipelines, and a real manager as the source of truth. Then build the two habits nearly everyone skips, which are rotating credentials on a schedule and revoking before investigating when something goes wrong.

Start with the inventory this week. It takes an afternoon, it is uncomfortable in a useful way, and everything else on this list becomes obvious once you can see what you actually have.

Ready to Build Real Secrets Management Skills?

Secrets management sits at the intersection of infrastructure, identity, and operational discipline, which makes it a genuinely valuable specialism. The HashiCorp Certified Vault Associate course on KodeKloud covers authentication methods, dynamic secrets engines, and access policies from the ground up, the Certified Kubernetes Security Specialist (CKS) course covers cluster secrets and RBAC in depth, and the KodeKloud playgrounds let you practise every command in this guide safely. Pick one and start today.

Playgrounds

KodeKloud Playgrounds

Clusters where you can create, decode, mount, and lock down Secrets, and practise every command in this guide without risk.

KubernetesSecurityDevOps
Launch a playground →

FAQs

Q1: What is the difference between an environment variable and a secret?

An environment variable is a delivery mechanism, while a secret is a category of data, and conflating the two causes most of the problems in this area. Environment variables are simply key value pairs passed to a process by its parent, and they can carry anything at all, including a log level, a region name, or a database password. A secret is any value that grants access, meaning something that would require you to take action if it were disclosed rather than merely being inconvenient. You can deliver a secret through an environment variable, and that is extremely common, but doing so gives the secret the security properties of the environment, which include being readable through process inspection, inherited by every child process, and captured by crash reporters. The useful mental model is that configuration answers how the application should behave while secrets answer what the application is allowed to reach, and those two questions deserve different storage, different access controls, and different lifecycles.

Q2: Are .env files safe to use, and when should I stop using them?

They are perfectly reasonable for local development and unsuitable as a production mechanism, and the boundary between those is worth being explicit about. Locally, a .env file kept out of version control is convenient, works with every framework, and is easy for new joiners to understand, particularly when you commit a template file documenting which variables are needed without any real values. The problems begin when the same file pattern moves into production, because the file sits unencrypted on disk, anyone with host access can read it, it is trivially copied to a laptop, and it has no audit trail showing who read it or when. There is also no rotation story, since changing a value means editing files across every machine that has one. The practical rule is that .env files are fine when the values are development credentials pointing at development systems with fake data, and they should never contain a credential that would matter if it leaked. For production, use your platform's mechanism, meaning mounted secrets in Kubernetes, a secrets manager fetched at runtime, or federated identity where no stored credential exists at all.

Q3: What do I need to know before setting up secrets management for a team?

The prerequisites are more about operational understanding than any specific tool. Know how your applications receive configuration today, because you cannot improve a delivery path you have not mapped. Understand identity in your environment, meaning how a workload proves who it is, since every modern secrets approach depends on a machine being able to authenticate without already holding a secret, which is the bootstrapping problem at the heart of this field. Be comfortable with access control in whichever platforms you run, particularly Kubernetes RBAC and cloud IAM, as the manager only helps if the permissions around it are tight. On the practical side, familiarity with encoding versus encryption prevents the base64 misunderstanding, and basic incident response thinking prepares you for the day something leaks. For structured learning, the HashiCorp Certified Vault Associate course on KodeKloud covers the concepts underlying most managers, and the Certified Kubernetes Security Specialist (CKS) course covers the cluster side.

Q4: Are Kubernetes Secrets actually secure?

They are secure only in combination with several settings that are not enabled by default, and understanding that precisely is important. A Secret object stores its data base64 encoded, which is an encoding rather than encryption, so anyone able to read the object can decode the value instantly with no key involved. By default the data is also stored unencrypted in etcd, meaning that access to the etcd datastore or to a backup of it exposes every secret in the cluster. What makes them genuinely usable is a combination of four things: enabling encryption at rest in the API server, ideally backed by a cloud key management service; restricting RBAC so that workloads and users can only read the specific Secrets they need rather than listing everything in a namespace; mounting secrets as files rather than injecting them as environment variables, since mounted files avoid process inspection and child process inheritance and they update when the Secret changes; and treating an external manager as the source of truth with the External Secrets Operator syncing values in. With those four in place, Kubernetes Secrets are a reasonable delivery mechanism. Without them, they are closer to a naming convention than a security control.

Q5: How often should secrets be rotated, and how do I do it without breaking things?

Frequency matters less than having a procedure you have actually executed, and the honest answer is that most teams should start by rotating anything highly privileged quarterly and anything else annually, then tighten from there. What makes rotation safe is designing for an overlap period where two credentials are valid simultaneously, so you add the new credential, migrate consumers to it gradually, verify through access logs that the old one is no longer being used, and only then revoke it. That converts a risky synchronised cutover into a rolling change you can pause. Two supporting practices matter as much as the procedure. Keep an inventory recording each secret, its owner, its consumers, and its last rotation date, because the fear around rotation is almost always uncertainty about what will break rather than technical difficulty. And rehearse rotation deliberately during a quiet period, since a team that has never rotated a credential calmly will not manage it well during an incident. The structural escape from all of this is dynamic secrets that expire automatically within hours, which makes expiry itself the rotation mechanism and removes the procedure entirely.

Q6: What should I do in the first hour after discovering a leaked credential?

Revoke first and investigate afterwards, because every minute spent understanding the leak is a minute the credential still works. Disable or rotate the exposed credential immediately, even before you know how it escaped, since a revoked credential is harmless regardless of who saw it. Next, assume it was used and check access logs from the moment of exposure onward, looking for unfamiliar source addresses, activity in regions you do not operate in, and API calls your applications never make, as credentials exposed publicly have been observed being picked up by automated scanners within minutes. Then find every other copy of the same value, checking other repositories, CI variables, container images, configuration management, wikis, and chat history, because the same credential is usually in more places than the one that alerted you. Rewriting Git history is worth doing for hygiene but belongs last, since it does nothing about an exposure that has already occurred. Finally, record which path the leak took and fix that specific path, because a commit, a build log, and an error report each need a different control and treating them all as human error fixes none of them.

Pramodh Kumar M Pramodh Kumar M

Subscribe to Newsletter

Join me on this exciting journey as we explore the boundless world of web design together.