DEV Community

Cover image for Docker for Absolute Beginners — Part 5
Md Enayetur Rahman
Md Enayetur Rahman

Posted on

Docker for Absolute Beginners — Part 5

Environment Variables, Interactive Flags, and Hot‑Reload Comforts

Part 4 explored volume flavours and production‑only installs. Today we wire *.env files** into Docker Compose, learn what stdin_open and tty: true really do, and keep our live‑reload Node loop humming along.*


Learning Aims

  1. Inject key/value pairs from a .env file into both your container and your Compose YAML (port mapping, secrets, feature flags).
  2. Understand how stdin_open: true and tty: true make a container behave like a friendly terminal—critical for interactive CLIs and nodemon.
  3. Read a Dockerfile and Compose file token‑by‑token, with extra focus on environment‑variable sections.

1 Project Snapshot

docker-with-env-file/
├─ Dockerfile
├─ docker-compose.yml
├─ .env               # PORT=3000
├─ package.json       # dev=nodemon script
└─ index.js           # prints Hello from port $PORT
Enter fullscreen mode Exit fullscreen mode

index.js uses dotenv to read the port from environment variables:

require("dotenv").config();
const express = require("express");
const app = express();
const PORT = process.env.PORT;

app.get("/", (_, res) => res.send(`Hello from port ${PORT}!`));
app.listen(PORT, () => console.log(`🚀 Server running on ${PORT}`));
Enter fullscreen mode Exit fullscreen mode

2 Dockerfile — Word‑by‑Word

FROM node:20-alpine            # 1
WORKDIR /usr/src/app           # 2

# 1️⃣ copy manifests first for npm cache
COPY package*.json ./          # 3
RUN npm i && npm cache clean --force   # 4

# 2️⃣ copy source last
COPY . .                       # 5

CMD ["npm", "run", "dev"]      # 6
Enter fullscreen mode Exit fullscreen mode
# Token Beginner‑friendly explanation
1 FROM node:20-alpine Start from a slim Alpine image with Node 20. Smaller download, faster build.
2 WORKDIR /usr/src/app Sets the current directory inside the image. Docker will create it.
3 COPY package*.json ./ Copies package.json and package-lock.json. Doing this before the rest of your code lets Docker reuse the dependency layer when only source files change.
4 RUN npm i && npm cache clean --force Installs deps, then wipes the npm cache to shed ≈20 MB. For dev we keep devDependencies so nodemon is available.
5 COPY . . Drops your source code into the image. Cached npm layer remains intact.
6 CMD [...] This specifies the command to run when a container is started from this image. In this case, we're running npm run dev, which in our package.json is configured to start the server using nodemon for automatic restarts on file changes.

3 docker-compose.yml — Token‑by‑Token

version: "3.9"
services:
  app:
    build: .
    container_name: env_demo

    command: npm run dev
    ports:
      - "${PORT}:${PORT}"

    env_file:
      - .env

    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules

    environment:
      CHOKIDAR_USEPOLLING: "true"
      CHOKIDAR_INTERVAL: "1000"

    stdin_open: true
    tty: true
Enter fullscreen mode Exit fullscreen mode

A docker-compose.yml file is used to define and run multi-container Docker applications. It's a powerful tool for managing the services, networks, and volumes for your application in a single file.

version: "3.9"
Enter fullscreen mode Exit fullscreen mode
  • version: This specifies the version of the Docker Compose file format we're using.
services:
Enter fullscreen mode Exit fullscreen mode
  • services: This is where we define the different services (containers) that make up our application. In this case, we only have one service, which we've named app.
  app:
    build: .
Enter fullscreen mode Exit fullscreen mode
  • build: .: This tells Docker Compose to build the image for this service using the Dockerfile in the current directory (.).
    container_name: env_demo
Enter fullscreen mode Exit fullscreen mode
  • container_name: This sets a custom name for the container, making it easier to identify.
    command: npm run dev
Enter fullscreen mode Exit fullscreen mode
  • command: This overrides the default CMD in the Dockerfile. Here, we're explicitly telling the container to run npm run dev.
    ports:
      - "${PORT}:${PORT}"
Enter fullscreen mode Exit fullscreen mode
  • ports: This maps a port on the host machine to a port in the container. The ${PORT} syntax is a variable that will be substituted with the value from our .env file. This allows us to access the application from our web browser.
    env_file:
      - .env
Enter fullscreen mode Exit fullscreen mode
  • env_file: This is a crucial part of our setup. It tells Docker Compose to load environment variables from a file named .env in the same directory.
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
Enter fullscreen mode Exit fullscreen mode
  • volumes: This creates a link between a directory on our host machine and a directory inside the container. This is incredibly useful for development because any changes we make to our code on our local machine will be immediately reflected inside the container without needing to rebuild the image.
    • The first volume (.:/usr/src/app) maps our project directory to the container's working directory.
    • The second volume (/usr/src/app/node_modules) is a way to prevent the node_modules directory on our local machine from overwriting the one inside the container.
    environment:
      CHOKIDAR_USEPOLLING: "true"
      CHOKIDAR_INTERVAL: "1000"
Enter fullscreen mode Exit fullscreen mode
  • environment: This allows us to set additional environment variables directly in the docker-compose.yml file. These variables are used to configure nodemon to watch for file changes correctly within the Docker environment.

4 Running the Stack

docker compose up
Enter fullscreen mode Exit fullscreen mode

What happens:

Compose reads .env in the project root, sets PORT=3000 for substitution.

Image builds (if absent) and container starts.

Nodemon launches on port 3000; visit http://localhost:3000 to see Hello from port 3000!.

Edit index.js → nodemon restarts thanks to bind mount.

Changing .env

Edit .env to PORT=4000.

Stop the container (Ctrl +C).

Re‑run docker compose up.

Compose rebuilds nothing, but passes the new port; mapping changes to 4000:4000 and Express listens on the new value.

Containers only read env vars at start‑time, so a restart is required.

5 What if .env Goes Missing?

${PORT} in ports: would expand to blank, causing a Compose error: “invalid published port”.

Inside the container process.env.PORT would be undefined, and Express would crash or fall back to a default if coded.

Always commit an example .env.sample and use CI to validate required vars.


6 Conclusion

✅ Feeding secrets & configs from .env files into both Compose tokens and container environments.✅ The role of stdin_open (‑i) and tty: true (‑t) for interactive containers.✅ Variable‑precedence: environment: > env_file: > project‑root .env.✅ How to restart containers to pick up updated env vars.

Keep Docker‑ing!

Top comments (0)