PTENES
MODULE 2.4

🔑 Environment Variables

Your API key, database password, secret token: none of these belong in your code. Environment variables are the safe where configuration is kept separate from the app. Here you’ll learn how to securely store secrets in .env, Vercel, and GitHub Actions.

6
Topics
45
Minutes
Basic
Level
Hands-on
Type
1

What Environment Variables Are and Why They Exist

Every application needs information to work: the database address, an API key, a service password. This information changes from one place to another. On your computer, the database is local. On Vercel, the database is in the cloud. A environment variable is a way to store these values outside the code, so the same app runs anywhere just by changing the configuration.

YOUR APP const key = process.env .API_KEY reads .env VAULT API_KEY=******** DB_URL=******** Local .env file Vercel Settings > Env Vars GitHub Actions Repo secrets

🧠 Analogy: The Vault Separate from the Code

Imagine your app is a house, and the code is the house’s blueprint. You can show the blueprint to anyone, publish it on the internet, and put it on GitHub. But the door key you don't draw it on the blueprint. You store it in a separate vault.

  • •The code and the blueprint: the same everywhere, it can be public.
  • •Environment variables are the key to the vault: secret, they change by location.
  • •Rotate the key (config) no requires rebuilding the house (the app).

How the code reads a variable

Instead of writing the value directly in the code, you read it from an environment variable. The standard name is process.env in Node.js.

# WRONG: secret in the code (anyone can see it)

const apiKey = "sk-live-9f8a7b6c5d4e3f2a1b0c";

# RIGHT: the code reads the environment variable

const apiKey = process.env.API_KEY;

The code stays the same everywhere. What determines the value of API_KEY is the environment where the app runs.

2

The Local .env File

On your computer, the vault has a name: the file .env (with a dot at the beginning). It’s a plain text file with one variable per line, in the format CHAVE=valor. It sits in the project root and never goes to GitHub.

Creating the .env file

# At the project root, create the file

$ touch .env

$ nano .env

# Inside .env, one variable per line:

API_KEY=sk-live-9f8a7b6c5d4e3f2a1b0c

DATABASE_URL=postgres://user:senha@host:5432/db

PORT=3000

Without quotes, without spaces around the =. By convention, names use ALL_CAPS_WITH_UNDERSCORES.

Reading .env with dotenv

Node.js doesn't read the file .env by itself. The library dotenv loads the file and puts the variables in process.env.

# Install the library

$ npm install dotenv

added 1 package in 1s

# At the top of your main file (index.js):

require('dotenv').config();

# Now process.env.API_KEY works

console.log(process.env.PORT);

3000

Frameworks like Next.js and Vite already read the .env automatically, without needing dotenv.

💡 Tip: Create a .env.example

How the .env doesn't go to GitHub, so anyone who clones the project won't know which variables to fill in. The solution is to create a .env.example with the keys but without the values (e.g.: API_KEY=). You commit this one: it serves as a template without leaking secrets.

⚠️ Common Error

Problem: "I set the variable in the .env but process.env.API_KEY appears as undefined."
Solution: There are usually three causes: (1) you forgot to call require('dotenv').config() at the top of the file; (2) the .env isn't in the root where the app runs; (3) you added a space or quotation marks in the value. Check all three and restart the app (variables are read only at startup).

3

Variables in Vercel

When the app deploys to Vercel, it doesn’t have your .env locally (which wasn't sent). So you need to add the same variables in the Vercel dashboard. They're stored securely and injected into the app with each deploy. Best of all: Vercel separates the variables by environment (Production, Preview, Development).

1

Open the project settings

In the Vercel dashboard, choose the project and go to Settings > Environment Variables.

2

Add each variable

Enter the name (Key) and value (Value), matching your .env.

# Example of a variable

Key: API_KEY

Value: sk-live-9f8a7b6c5d4e3f2a1b0c

3

Choose the environment

Select where the variable applies: Production (live site), Preview (branch deployments) and/or Development (locally with vercel dev). You can have a test database in Preview and the real database only in Production.

4

Make a new deploy

Variables take effect in the next build. If you added a variable after deployment, you need to do a Redeploy for it to take effect.

👁 What you’ll see on the screen

On the Environment Variables page, each variable you've added appears as a row. The value is hidden (shown as dots), and you can see which environments it's active in:

# List of variables in the dashboard

API_KEY •••••••• Production, Preview

DATABASE_URL •••••••• Production

NEXT_PUBLIC_URL myapp.com All Environments

In Next.js, variables with the prefix NEXT_PUBLIC_ are visible in the browser. Use them only for values that aren't secret (such as the site’s public URL).

4

Variables in GitHub Actions

If you use GitHub Actions to automate tests or deploys, the scripts also need secrets: a deploy token, an API key, a database password. GitHub stores these values in Secrets from the repository. They're encrypted, nobody can read them, and you use them inside the workflow.

Adding a Secret

In the GitHub repository, go to Settings > Secrets and variables > Actions and click New repository secret.

# Example of a registered secret

Name: DEPLOY_TOKEN

Secret: ghp_xxxxxxxxxxxxxxxxxxxx

After saving, GitHub never shows the value again. If you forget it, just add it again.

Using the Secret in the workflow

Inside the workflow file (.github/workflows/deploy.yml), you read the secret using the syntax ${{ secrets.NOME }}.

name: Deploy

on: [push]

jobs:

build:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- name: Deploy

# injects the secret as an environment variable

env:

API_KEY: ${{ secrets.API_KEY }}

DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

run: npm run deploy

Inside the step, the app reads process.env.API_KEY as usual. The secret never appears in the logs: GitHub automatically hides the value.

💡 Tip: Secrets vs Variables

GitHub has two tabs: Secrets (secret values, hidden forever) and Variables (non-secret, visible values). Use Secrets for tokens, keys, and passwords. Use Variables (handled with ${{ vars.NOME }}) for public settings, such as the environment name or region.

5

Security: What to NEVER Commit

The golden rule is simple: no secret goes to Git. When you commit a file, it becomes part of the history forever, and if the repository is public, anyone in the world can see it. Bots scan GitHub all day looking for leaked keys to abuse. The main defense is the file .gitignore.

.gitignore protects your .env

O .gitignore is a list of files that Git should ignore. Add the .env in this list before any commit.

# Contents of the .gitignore file

.env

.env.local

.env*.local

node_modules/

# Confirm that Git is ignoring:

$ git status

nothing to commit, working tree clean

# .env does NOT appear in the list. Protected.

✓ You can commit

  • ✓O .gitignore (the exclusion list)
  • ✓O .env.example (keys only, without values)
  • ✓Code that reads process.env
  • ✓Public config (site URL, app name)

✗ NEVER commit

  • ✗The file .env with real values
  • ✗API keys and tokens in code
  • ✗Database passwords
  • ✗Credential files (.pem, .key, service account JSON)

⚠️ Common Error

Problem: "I accidentally committed the .env before creating the .gitignore. I deleted the file and committed again. Is it fixed?"
Solution: No. The value remains in the history from Git and can be recovered by anyone. Deleting it now won't help. Treat the key as leaked: revoke it and generate a new one in the service (see topic 6). Rewriting the history (with git filter-repo) helps, but if it has already been pushed to a public GitHub repository, consider the secret compromised.

6

Secrets Rotation

Even when kept safe, a secret shouldn't last forever. Rotate is to replace the key with a new one from time to time and revoke the old one. That way, if a key leaks without you noticing, it has a short expiration period. And if you you know that has leaked, rotation is immediate and mandatory.

🧠 Analogy: Changing the Lock

You’d never know if a former tenant had made a copy of your house key. That’s why, when a tenant moves out, you changes the lock: all the old copies stop working at once. That's exactly what rotating a secret means. The new key takes effect; the old one becomes useless, even if someone has a copy.

1

Generate the new key

In the service dashboard (Supabase, Stripe, etc.), create a new key. Most services keep both keys active for a while so the switch doesn’t break the app.

2

Update in all locations

Change the value in .env locally, in the Vercel dashboard, and in GitHub Secrets. All three need to point to the new key.

# Places where the secret lives

.env (local)

Vercel > Settings > Environment Variables

GitHub > Settings > Secrets

3

Redeploy and test

Upload the new version and confirm that the app works with the new key. Only then move on to the final step.

4

Revoke the old key

In the service dashboard, delete or deactivate the old key. From this point on, any old copy stops working. This is the step that actually closes the door.

✓ Best practices

  • ✓Rotate important secrets every 60-90 days
  • ✓IMMEDIATELY revoke any key that has leaked
  • ✓Use different keys for each environment (dev / prod)
  • ✓Give each key only the permissions it needs

✗ Avoid

  • ✗Use the same key for years without rotating it
  • ✗Sharing secrets over chat or email
  • ✗Reuse the production key in the test environment
  • ✗Keep the old key active “just in case”

🏆 You’ve completed Track 2!

You now know how to deploy an app on Vercel, connect a database on Supabase, consume APIs, and now store secrets securely. This is the complete modern deploy cycle: code in Git, app in the cloud, data in the database, and configuration in the vault. In the next track, you’ll go one level deeper and learn to run everything on your own own server.

📚 Module Summary

✓
Environment variable = vault outside the code - config changes without touching the app
✓
.env local with KEY=value - read by dotenv, never committed
✓
Vercel: Settings > Environment Variables - by environment (Production / Preview / Development)
✓
GitHub Actions: Repo Secrets - read with ${{ secrets.NOME }}
✓
.gitignore protects; rotation revokes - no secrets go into Git; rotate a leaked key immediately

Next Track:

Track 3 - Your Own Server (VPS, SSH, firewall, and tokens: run everything on your own machine in the cloud)