Short answer
- Manage secrets by keeping them out of code: .env is listed in .gitignore, and the real values live in the environment variables of your CI platform and your server.
- In Docker, never put secrets in ARG or ENV in the Dockerfile, because they stay in the image layers; use Docker secrets or --mount=type=secret instead.
- Scan every commit with gitleaks, in pre-commit and in CI, so a secret never reaches the history.
- If a secret did reach git, rotate it immediately and only then rewrite the history with git filter-repo.
- First action after a leak
- rotate the secret, do not just delete it from the repo
- Where not to put secrets in a Dockerfile
- ARG and ENV, they stay in the image layers
- GDPR deadline to notify a breach
- 72 hours to ANSPDCP
Why deleting it from the repo fixes nothing
Git keeps every version of every file. A commit that deletes .env leaves the previous version reachable through history, in every clone, fork or mirror. Attackers' scanners extract cloud keys, payment tokens and database passwords from new commits within minutes. Even in a private repository, everyone who ever had access holds a copy. The order is fixed: rotate the secret first, then clean the history with git filter-repo, then look for the cause.
Where secrets live in each environment
Locally they sit in .env, ignored by git, with a committed .env.example that lists the keys without values. In CI they sit in the platform's secret variables, masked in logs. On the server they go in a systemd EnvironmentFile with 600 permissions or in a secrets manager read at startup. In Docker, a secret written in ENV appears in docker inspect and in every image layer; mount it as a file through Docker secrets or, at build time, with --mount=type=secret.
What Romanian law says
A leaked key that gives access to personal data is a security breach under GDPR: Article 33 requires notifying ANSPDCP within 72 hours of discovery, and Article 32 requires the technical measures that would have prevented it. A leaked SPV access token or e-Factura certificate means a third party can read or issue invoices in your company's name. For entities registered with the DNSC under OUG 155/2024, managing cryptography and access is among the measures required by Article 11 of OUG 155/2024.
Where a secret lives in each environment and what is forbidden| Environment | Correct place | Forbidden |
|---|
| Local, development | .env ignored by git, .env.example without values | Real values in .env.example or in code |
|---|
| CI/CD | Secret variables of the platform, masked in logs | Secrets in pipeline files inside the repo |
|---|
| Docker build | --mount=type=secret | ARG or ENV carrying secret values |
|---|
| Docker runtime | Docker secrets mounted as a file | ENV in the Dockerfile or in a committed docker-compose file |
|---|
| Team | Vault, SOPS + age, the platform's manager | Chat, e-mail, shared documents |
|---|
Commands and configuration
.gitignore and scanning before commit# .gitignore
.env
.env.*
!.env.example
# pre-commit
gitleaks protect --staged --redact
Docker: a build-time secret with no trace in the image# Dockerfile
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci --omit=dev --ignore-scripts
# build
docker build --secret id=npm_token,src=./npm_token.txt -t example.ro/app .
systemd: environment file with restrictive permissions# /etc/example/app.env (chmod 600, owner root)
DATABASE_URL=postgres://app:...@127.0.0.1/example_db
# in the systemd unit
EnvironmentFile=/etc/example/app.env
Practical examples
Web agency in Timisoara
A developer committed .env with a payment processor's production key to a public portfolio repository. Within hours there were attempted transactions from other countries. The key was rotated at once, and the history was cleaned afterwards.
Accounting firm in Bucharest with an e-Factura integration
The SPV access token was written as ENV in the Dockerfile and ended up in the firm's image registry, open to every employee. It was moved to Docker secrets and rotated, and access to the registry was narrowed.
Common mistakes
- Believing the problem is solved once .env is removed from the repo, when the secret stays in history and must be rotated.
- Putting secrets in ARG or ENV in the Dockerfile and pushing them to the image registry with every push.
- Using the same key in every environment and every project.
How 4hosting helps
- Hardening & Compliance explicitly covers access, secrets and keys: moving them out of code into a secrets manager and rotating them on a schedule.
- CI/CD & Monitoring sets up pipelines with secrets held in the platform and gitleaks scanning; Docker & Kubernetes builds images with no secrets in their layers.
- The DevOps hours included each month, 1–8 on VPS, 5–10 on dedicated servers depending on the plan and 10 on GPU servers, are requested from the client panel, in the “DevOps Services” section; a secrets audit usually fits inside them.
Related questions
I pushed an API key to GitHub. What do I do, in order?
Rotate the key at the provider and immediately check the usage log for foreign activity. Then rewrite history with git filter-repo and force-push, telling collaborators to re-clone. Finally add gitleaks to pre-commit and CI. If the key gave access to personal data, assess your notification duty.
Are environment variables safe on a server?
They are acceptable if the file they come from has 600 permissions and belongs to root, and the process runs as a dedicated user. They remain visible in /proc to root and in crash dumps. For critical secrets, a secrets manager with read-at-startup and auditing is better.
How are Docker secrets different from ENV?
ENV is written into the image and container metadata, visible through docker inspect and in every image layer. Docker secrets mount the secret as an in-memory file under /run/secrets, only in the containers that declare it, without appearing in the image or in logs.