Git Course 0%

Secrets, .env and .gitignore

Intermediate Core Git Git CLI GitLab UI GitHub UI ≈ 9 min

What you will learn

  • What is a secret and what is merely configuration
  • The .env plus .env.example pattern, and why it works
  • Where real values live: CI/CD variables, secrets, and your own machine

After this lesson you can

  • I can add a new setting to a project without ever putting its value in Git

Why this matters

Git is designed to remember everything forever and to copy itself to every colleague's laptop and every build machine. That is exactly wrong for a password. The good news is that keeping secrets out is a habit, not a technology, and the habit is three lines long.

What counts as a secret

Secret: never in Git Configuration: fine in Git
Passwords, API keys, access tokens Which port the app listens on
Private keys (.pem, id_ed25519) Feature flags and defaults
Database connection strings with credentials The names of the settings that exist
Signing certificates Timeouts, limits, log levels
.env files with real values .env.example with empty values

The test: if it appeared on a public page, would you have to change something? If yes, it is a secret. A port number is not; a token is; a database host name is a judgement call and usually belongs with the secrets.

Note the last row. The list of settings is useful to everyone and belongs in the repository; only the values are secret.

The example-file pattern

Two files with almost the same name, and the whole convention rests on the difference:

.env.example — committed
DATABASE_URL=
TRAILGUIDE_API_KEY=
LOG_LEVEL=info
.env — ignored, exists only on your machine
DATABASE_URL=postgres://localhost/trailguide
TRAILGUIDE_API_KEY=sk-live-9f8e7d6c5b4a
LOG_LEVEL=debug

And one line in .gitignore, which the playground already has:

.gitignore (excerpt)
# Secrets and local settings — never commit these
.env
*.local

A new colleague copies .env.example to .env, fills in the values from the team's password manager, and is running in five minutes. Nobody had to send a token in a chat message, and Git never saw one.

Git enforces the rule for you:

Terminal
$ git status --short

Nothing. The file exists on disk and Git looks away:

Terminal
$ git check-ignore -v .env
.gitignore:7:.env	.env

And if you try to add it anyway:

Terminal
$ git add .env
The following paths are ignored by one of your .gitignore files:
.env
hint: Use -f if you really want to add them.
hint: Disable this message with "git config set advice.addIgnoredFile false"

Where the real values live

Place For
Your machine, in an ignored .env Local development
The team's password manager Sharing between people
GitLab: Settings → CI/CD → Variables (mark Masked and Protected) Values the pipeline needs
GitHub: Settings → Secrets and variables → Actions The same on GitHub
The platform's environment settings, or a dedicated vault Values the running application needs in production

The pipeline case is the one you will meet as a non-developer, because a documentation deployment often needs a token. Both platforms inject those values as environment variables at job time and mask them in the logs, so they never appear in the repository or the output.

How to do it

Terminal
$ cp .env.example .env          # then fill in the values
$ git check-ignore -v .env       # prove Git is ignoring it
$ git status --short             # .env must not appear

Before any commit that touches configuration, git diff --staged and read it. A secret in a diff is much easier to catch than a secret in history.

Common mistakes

  • Committing .env "temporarily". There is no temporary in Git; the commit is permanent unless history is rewritten (lesson 8.7).
  • Adding .env to .gitignore after committing it. Ignore rules do not apply to tracked files; git rm --cached is needed, and the old commits still hold the value.
  • Putting a token in a URL such as https://user:token@gitlab.com/…. It lands in .git/config and in shell history.
  • Sharing values in chat. Use the password manager; chat is searchable and backed up.
  • Assuming a private repository is safe enough. It is better than public, but everyone with read access, every fork, every clone and every backup has the value.

Try it yourself

Goal: prove that the ignore rule works before you ever need it to.

  1. In the playground, create a file with a fake secret: printf 'API_KEY=sk-live-fake123\n' > .env.
  2. Run git status --short. It should print nothing.
  3. Run git check-ignore -v .env and read which rule matched.
  4. Try git add .env and read the refusal.
  5. Delete the file: rm .env.

Expected result: step 2 is silent, step 3 names .gitignore:7:.env, and step 4 refuses with the -f hint.

Show solution

The silence in step 2 is the protection working: a file that never appears in git status cannot be committed by git add .. If your playground's .gitignore lacks the .env line, add it now; every real project should have one.

Check yourself

1. Which of these belongs in the repository?
2. git add .env prints "The following paths are ignored". What should you do?
3. A pipeline needs a deploy token. Where does it go?

Key terms

.gitignore Ignored Environment variable Configuration