hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Deploying Node.js Apps with Docker

Why Docker

  • The "Works on My Machine" Problem
  • Docker Concepts

Your First Dockerfile

  • Writing a Dockerfile
  • Multi-Stage Builds
  • The .dockerignore File

Running Containers

  • Running and Managing Containers
  • Environment Variables and Secrets
  • Health Checks

Multi-Container Apps

  • Docker Compose
  • Adding a Reverse Proxy
  • Persistent Data

Deploying to Production

  • Deploying to a VPS
  • HTTPS with Let's Encrypt
  • Zero-Downtime Deploys

CI/CD

  • Building Images in CI
  • Automated Deployment

Production Hardening

  • Container Security
  • Logging and Monitoring
  • Deployment Checklist and Capstone

Environment Variables and Secrets

Configuration via environment variables

Every app has configuration that changes between environments: database URLs, API keys, feature flags, port numbers. These should come from environment variables, not from code.

const PORT = parseInt(process.env.PORT ?? "3000");
const DATABASE_URL = process.env.DATABASE_URL ?? "app.db";
const NODE_ENV = process.env.NODE_ENV ?? "development";

Setting env vars at runtime

Pass environment variables when starting the container:

# Individual variables
docker run -d \
  -p 3000:3000 \
  -e NODE_ENV=production \
  -e DATABASE_URL=/data/app.db \
  -e JWT_SECRET=your-secret-here \
  myapp

# From a file
docker run -d \
  -p 3000:3000 \
  --env-file .env.production \
  myapp

The --env-file flag reads key=value pairs from a file:

# .env.production
NODE_ENV=production
DATABASE_URL=/data/app.db
JWT_SECRET=change-me-in-production
URL_SECRET=another-secret

Never bake secrets into images

# NEVER DO THIS
ENV JWT_SECRET=my-super-secret

This embeds the secret in the image. Anyone with access to the image can read it:

docker inspect myapp | grep JWT_SECRET
# "JWT_SECRET=my-super-secret"  ← exposed!

Images are pushed to registries, shared with team members, and stored in CI caches. A secret in the image is a secret shared with everyone.

Instead, pass secrets at runtime with -e or --env-file. The secret exists only in the running container’s environment, not in the image itself.

Build-time vs runtime variables

Build-time (ARG): Available during docker build. Used for build configuration (Node version, build flags). Not available in the running container.

ARG NODE_VERSION=22
FROM node:${NODE_VERSION}-alpine
docker build --build-arg NODE_VERSION=20 -t myapp .

Runtime (ENV): Available in the running container. Used for non-secret configuration (port, environment name). Baked into the image — visible with docker inspect.

ENV NODE_ENV=production
ENV PORT=3000

Neither (-e flag): Passed only at runtime. Not in the image. Used for secrets.

The rule: ARG for build config, ENV for non-secret defaults, -e for secrets.

Docker Compose env files

Docker Compose (covered in the next section) supports .env files natively:

# docker-compose.yml
services:
  app:
    image: myapp
    env_file:
      - .env.production

Exercises

Exercise 1: Run your container with -e NODE_ENV=production. Exec into it and verify: docker exec myapp sh -c 'echo $NODE_ENV'.

Exercise 2: Create a .env.production file. Run with --env-file .env.production. Verify the variables are set inside the container.

Exercise 3: Try baking a secret with ENV SECRET=test in the Dockerfile. Build. Run docker inspect myapp | grep SECRET. See the problem? Remove it.

Why should secrets never be set with ENV in a Dockerfile?

← Running and Managing Containers Health Checks →

© 2026 hectoday. All rights reserved.