Docker Build Secrets: Managing Arguments and ENV Variables Securely
Learn to securely pass environment variables during Docker builds for Node.js apps. Explore ARG/ENV, CI/CD workflows with GitHub Actions and GitLab, and best practices for managing secrets effectively!

When building modern applications, particularly Node.js-based ones, managing environment-specific configurations and secrets securely is critical. From API keys to database URLs, these sensitive values are integral to deploying robust and secure containerized applications.
This blog explores why passing variables during Docker builds is crucial for Node.js applications, and how CI/CD platforms like GitHub Actions or GitLab CI can securely inject secrets into your builds. By the end, you'll understand how to seamlessly manage and pass arguments and environment variables in Docker while adhering to best practices.
Why Passing Variables During Docker Builds is Important
In Node.js applications, environment variables (ENV
) are commonly used to store secrets, configurations, and runtime-specific settings. However, when these applications are bundled into Docker images and compiled (e.g., using yarn build
), runtime environment variables cannot modify the build output. This is because:
- Static Compilation: During the build process (e.g., for frontend frameworks or server setups), environment variables must be baked into the code. They can't be dynamically injected at runtime.
- Immutable Images: A Docker image is a snapshot of the application at a specific state, making post-build variable injection ineffective for some of the application where static compilation happens.
As a result, environment variables or build arguments must be passed at build time to ensure they are available when the application runs.
Using ARG
and ENV
in Docker
Docker provides two mechanisms for injecting variables into the build process:
ARG
: Used to define build-time variables, accessible only during the image build process.ENV
: Defines runtime environment variables that the container can use.
Here’s a practical example:
# Use an official Node.js runtime as the base image
FROM node:22-alpine
# Set the working directory
WORKDIR /app
# Accept build arguments for environment variables
ARG NODE_ENV
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY
ARG DB_URL
# Set environment variables during the build
ENV NODE_ENV=${NODE_ENV}
ENV AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
ENV AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
ENV DB_URL=${DB_URL}
# Install dependencies
COPY package.json yarn.lock ./
RUN yarn install
# Copy application code and build
COPY . .
RUN yarn build
# Expose the application port
EXPOSE 3000
# Start the application
CMD ["yarn", "start"]
And here are the commands defined in the package.json
for your information.
"scripts": {
"dev": "nodemon src/index.ts",
"build": "tsc && copyfiles -u 1 src/templates/**/* dist",
"start": "node dist/index.js",
"format": "prettier . --write"
}
In the above example:
ARG
variables are passed during thedocker build
command.ENV
maps theseARG
variables for runtime use.
Passing Arguments During Build
To pass variables while building the image:
docker build \
--build-arg NODE_ENV=production \
--build-arg AWS_ACCESS_KEY_ID=my-access-key \
--build-arg AWS_SECRET_ACCESS_KEY=my-secret-key \
--build-arg DB_URL=my-database-url \
-t app .
Using GitHub Actions or GitLab CI for Secrets Management
When deploying on CI/CD platforms, securely injecting secrets into your Docker builds is crucial. Both GitHub Actions and GitLab CI/CD provide robust mechanisms for managing secrets.
Example: GitHub Actions
Here’s how you can pass secrets into a Docker build using GitHub Actions:
name: Build and Push Docker Image
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Log in to Docker Hub
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login --username ${{ secrets.DOCKER_USERNAME }} --password-stdin
- name: Build, Tag, and Push Docker Image
env:
NODE_ENV: production
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
DB_URL: ${{ secrets.DB_URL }}
run: |
docker build \
--build-arg NODE_ENV=$NODE_ENV \
--build-arg AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
--build-arg AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
--build-arg DB_URL=$DB_URL \
-t <your-docker-registry>:latest .
docker push <your-docker-registry>:latest
- Secrets: Stored in GitHub under Settings → Secrets. Secrets like
AWS_ACCESS_KEY_ID
andAWS_SECRET_ACCESS_KEY
are securely injected into the workflow. - Build and Push: The workflow passes secrets as
ARG
values to thedocker build
command, ensuring they are securely included in the image.
Example: GitLab CI/CD
For GitLab CI/CD, the process is similar. Here’s an example .gitlab-ci.yml
:
stages:
- build
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build \
--build-arg NODE_ENV=$NODE_ENV \
--build-arg AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
--build-arg AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
--build-arg DB_URL=$DB_URL \
-t <your-docker-registry>:latest .
- docker push <your-docker-registry>
variables:
NODE_ENV: production
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
DB_URL: $DB_URL
- Protected Variables: Secrets are added under Settings → CI/CD → Variables in GitLab. These are injected into the pipeline as environment variables.
- Docker Build: Similar to GitHub Actions, the
docker build
command uses these variables asARG
values.
Best Practices for Managing Secrets in Docker Builds
- Avoid Hardcoding Secrets: Never hardcode sensitive values in your
Dockerfile
or application code. - Use Secret Management Tools:
- GitHub Secrets or GitLab Variables for CI/CD.
- Docker Secrets for production environments.
- Restrict ARG Usage: Avoid exposing sensitive data in
ARG
, as it can be cached in Docker image layers. Use runtime secrets wherever possible. - Regular Rotation: Rotate secrets periodically to minimize risk.
- Minimal Image Layers: Combine commands in your
Dockerfile
to avoid creating layers with exposed secrets.
Passing arguments and environment variables during Docker builds is essential for Node.js applications, especially when compiling static builds or dealing with CI/CD pipelines. Tools like GitHub Actions and GitLab CI provide a secure and automated way to inject secrets into the build process. By following best practices and using the examples shared here, you can ensure a secure and seamless development-to-deployment workflow for your containerized applications.
That's it for today. See ya 👋