

Sarthak Varshney is a Docker Captain, 5x C# Corner MVP, and 2x Alibaba Cloud MVP, with over six years of hands-on experience in the IT industry, specializing in cloud computing, DevOps, and modern application infrastructure. He is an Author and Associate Consultant, known for working extensively with cloud platforms and container-based technologies in real-world environments.
So here's a situation I'm sure you've been in before.
You're writing an app. It connects to a database. You hardcode the password right there in the code — just temporarily, you tell yourself — and push it to GitHub. Two hours later, GitHub sends you a security alert. Your credentials got scraped by a bot. Congratulations, you've just had your first real encounter with why environment variables exist.
That scenario plays out constantly, by the way. Even experienced developers have done it at least once. But the good news is: once you understand environment variables and how Docker handles them, you'll never want to go back to hardcoding secrets ever again.
Let's talk about what environment variables are, why they matter, and how Docker makes working with them actually pretty clean.
Think of your application like a new hire on their first day at a job. They know how to do their work — but they don't know things like: Which database server do we use? What's the Wi-Fi password? Where's the coffee machine? That information is specific to the environment they're working in, not the job itself.
Environment variables are exactly that — they're configuration values that exist outside your code, injected into the running environment of your app. Your app just reads them when it needs them, without caring where they came from.
A few examples of what typically lives in environment variables:
DEBUG=true or ENV=productionThe core idea is simple: your code stays the same, but your configuration changes depending on where the app is running. In development, you connect to a local database. In production, you connect to a real one. Same code, different environment variables.
A fair question. Let's answer it properly.
Reason 1: Security. The moment you hardcode a database password or an API key into your source code, that secret becomes part of your codebase. And codebases get shared — with teammates, pushed to GitHub, copied to CI/CD pipelines. One accidental public repo and you're rotating credentials at 2 AM.
Reason 2: Flexibility. What if your app needs to run in three environments — development, staging, and production? If the database URL is hardcoded, you're either maintaining three different versions of your code or doing awkward find-and-replace every time you deploy. Environment variables let you configure once, deploy anywhere.
Reason 3: Professionalism. Any app that's meant to run in the real world separates code from configuration. It's not even debatable in professional engineering — it's just how things are done. The Twelve-Factor App methodology, which is a widely-used set of best practices for building modern software, has "Store config in the environment" as literally Factor III.
When you run a container, it's essentially a small isolated Linux environment. That environment has its own set of variables, separate from your host machine. By default, your container knows nothing about what's on your laptop.
Docker gives you a couple of ways to get configuration into a container. The most immediate one is the -e flag.
-e FlagThe -e flag (short for --env) lets you pass an environment variable directly to a container when you run it:
docker run -e MY_VARIABLE=hello nginx
Inside that container, MY_VARIABLE will be available as hello. That's it. That's the whole mechanic — simple and direct.
You can pass multiple variables by using -e multiple times:
docker run \
-e APP_PORT=8080 \
-e APP_ENV=development \
-e APP_DEBUG=true \
nginx
The backslashes (\) there are just a line-continuation character — it's the same command, just broken across lines so it's readable. You'll see this style a lot in Docker commands.
MySQL is one of the best examples to learn this with, because the official MySQL Docker image requires you to pass certain environment variables — it won't even start without them. This isn't a weird Docker quirk; it's by design. The image was built to be configured at runtime, not to have secrets baked in.
Here's the basic command:
docker run -d \
--name my-mysql \
-e MYSQL_ROOT_PASSWORD=supersecret \
-e MYSQL_DATABASE=myappdb \
-e MYSQL_USER=myappuser \
-e MYSQL_PASSWORD=userpassword \
-p 3306:3306 \
mysql:8.0
Let's break this down piece by piece:
-d — Detached mode. The container runs in the background and hands you back your terminal.
--name my-mysql — Gives the container a friendly name. Instead of referring to it by a random ID, you can use my-mysql in other commands.
-e MYSQL_ROOT_PASSWORD=supersecret — This is mandatory. The MySQL image will refuse to start without a root password. This sets it.
-e MYSQL_DATABASE=myappdb — Optional, but handy. Tells MySQL to automatically create a database named myappdb on startup. Saves you from having to connect and create it manually.
-e MYSQL_USER=myappuser and -e MYSQL_PASSWORD=userpassword — Creates a non-root user with the given credentials. Best practice: your app should connect as this user, not as root.
-p 3306:3306 — Maps port 3306 on your laptop to port 3306 inside the container, so you can connect to it from outside.
mysql:8.0 — The image to use. We're pinning to version 8.0 rather than latest so we know exactly what we're getting.
After running that command, you should get back a container ID (a long hex string). You can verify it's running with:
docker ps
You'll see my-mysql in the list with a status of Up.
Now let's actually connect to it:
docker exec -it my-mysql mysql -u myappuser -p
It'll prompt you for the password. Type userpassword and hit enter. You should be dropped into the MySQL shell. Try running:
SHOW DATABASES;
You'll see myappdb in the list. It was created automatically from the environment variable you passed in. Pretty neat.
Type exit to leave the MySQL shell.
Curious what variables are actually set inside a running container? You can check with:
docker exec my-mysql env
This runs the env command inside the container and prints out all the environment variables. You'll see your MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, and others in there.
You can also inspect a specific variable:
docker exec my-mysql printenv MYSQL_DATABASE
Output: myappdb
--env-file Option: A Cleaner Way to Do ThisPassing -e flags works fine when you have two or three variables. But what happens when your app has fifteen configuration values? Your docker run command turns into a wall of text that's impossible to read.
That's where --env-file comes in. You write all your variables in a plain text file and hand the whole file to Docker at once.
Create a file called .env:
MYSQL_ROOT_PASSWORD=supersecret
MYSQL_DATABASE=myappdb
MYSQL_USER=myappuser
MYSQL_PASSWORD=userpassword
Then run the container like this:
docker run -d \
--name my-mysql \
--env-file .env \
-p 3306:3306 \
mysql:8.0
Same result, much cleaner command.
Important: Add .env to your .gitignore file. This is where your actual secrets live, and it should never be committed to version control. The whole point of env files is that they stay on the machine, not in the repository.
Let's say you're building a Node.js app that connects to the MySQL container above. Inside your app code, you'd read environment variables like this:
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});
Notice process.env.DB_HOST || 'localhost' — that pattern gives a fallback value if the variable isn't set. Useful for local development where you might not want to set up a full env file just to run a quick test.
When you later containerize this Node.js app and run it alongside MySQL, you'd pass:
docker run -d \
--name my-node-app \
-e DB_HOST=my-mysql \
-e DB_USER=myappuser \
-e DB_PASSWORD=userpassword \
-e DB_NAME=myappdb \
my-node-app:latest
(Note: DB_HOST=my-mysql works when both containers are on the same Docker network — containers can refer to each other by name. That's a whole other topic worth exploring.)
Mistake 1: Committing your .env file to Git.
Seriously, this is the most common one. Add .env to .gitignore before you even create the file. Make it a habit.
# .gitignore
.env
.env.local
.env.production
Mistake 2: Passing sensitive values in docker inspect-visible form.
When you use -e, the value is visible to anyone who can run docker inspect <container>. For true production secrets, consider using Docker Secrets (available in Swarm mode) or a dedicated secrets manager like HashiCorp Vault or AWS Secrets Manager. For local development and learning, -e is fine.
Mistake 3: Forgetting to restart the container after changing env variables.
Environment variables are set at container start time. If you change your .env file, the running container doesn't automatically pick up the changes. You need to stop and recreate it:
docker stop my-mysql
docker rm my-mysql
docker run -d --env-file .env -p 3306:3306 --name my-mysql mysql:8.0
This is one of the things beginners find annoying at first but quickly get used to. The container's environment is frozen at the moment it was created.
Mistake 4: Typos in variable names.
MYSQL_ROOT_PASWORD instead of MYSQL_ROOT_PASSWORD will silently fail. The container starts, but MySQL doesn't see the variable it needs and either errors out or uses a default. Always double-check your variable names against the image's official documentation.
Mistake 5: Using spaces around the = sign in env files.
# This WILL NOT work
DB_HOST = localhost
# This WILL work
DB_HOST=localhost
No spaces. Just the key, the equals sign, and the value.
Here's a handy sequence to confirm your MySQL container is fully configured and accessible:
# 1. Check the container is running
docker ps
# 2. Check the logs for any startup errors
docker logs my-mysql
# 3. Verify the environment inside the container
docker exec my-mysql printenv MYSQL_DATABASE
# 4. Connect as root to verify full access
docker exec -it my-mysql mysql -u root -psupersecret
# Once inside MySQL:
SHOW DATABASES;
SELECT user, host FROM mysql.user;
EXIT;
If all of those work cleanly, your container is correctly configured.
Now that you know how to use environment variables, a word on using them well:
Never use production passwords locally. Use separate credentials for dev, staging, and production. If your laptop gets compromised, you don't want an attacker walking into your production database.
Rotate credentials regularly. Especially for anything internet-facing.
Don't echo secrets in scripts. If you're writing shell scripts that use env variables, be careful about echo $MYSQL_ROOT_PASSWORD or similar — this prints the secret to the terminal and potentially to log files.
In production, look into Docker Secrets or a proper vault. The -e flag is great for learning and development. For a real production system, you'll want something more robust.
Here's a hands-on challenge to cement what you've learned. No peeking at the solution until you've genuinely tried it!
Goal: Run a MySQL container and verify you can connect to it using a custom database and user — all configured via environment variables.
Steps:
Create a .env file with the following variables (use your own values):
MYSQL_ROOT_PASSWORDMYSQL_DATABASE (call it challenge_db)MYSQL_USER (call it challenge_user)MYSQL_PASSWORDRun a MySQL 8.0 container using --env-file with port 3307 on the host (not 3306, to avoid conflicts if you have MySQL installed locally).
Connect to the running container using docker exec and log in as challenge_user.
Run SHOW DATABASES; — confirm challenge_db is listed.
Create a table inside challenge_db:
USE challenge_db;
CREATE TABLE students (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100));
INSERT INTO students (name) VALUES ('You!');
SELECT * FROM students;
Bonus: Add a APP_ENV=development variable to your .env file and verify you can read it inside the container using printenv.
Double bonus: Stop and remove the container, then re-run it. Notice that your table is gone — because container storage is ephemeral. (That's a preview of why Docker Volumes exist — the next big topic!)
By the end of this one, you should be comfortable with:
-e--env-file for cleaner configurationEnvironment variables might seem like a minor topic — "it's just configuration" — but they're actually one of the most important habits you'll build as a developer. Code that cleanly separates secrets from logic is code that can be safely shared, versioned, and deployed without waking up at 2 AM to rotate leaked credentials.
Next up: Docker Volumes — because right now, anything you create inside a container disappears the moment it's removed. We'll fix that.
This article is part of an ongoing Docker series for beginners. If you missed the previous ones on container lifecycle, port mapping, Docker logs, and docker exec — go check those out first, they'll make this click even better.