Secrets management is one of the most commonly mishandled aspects of containerized applications. Embedding passwords, API keys, or tokens directly in a Dockerfile or committing them to a repository exposes credentials to anyone who can read the image history or the source tree. RHEL 8 gives you several layers of defense: environment variable files, Docker Swarm secrets, Docker BuildKit’s build-time secret mount, and integration with external vaults. This tutorial covers each approach so you can choose the right tool for each situation.

Prerequisites

  • RHEL 8 with Docker CE installed and the daemon running
  • Docker Compose v2 installed: dnf install -y docker-compose-plugin
  • For Swarm secrets: a Swarm initialized with docker swarm init
  • For BuildKit secrets: Docker 18.09+ (BuildKit is included in Docker CE on RHEL 8)
  • Optional: HashiCorp Vault installed or accessible at http://vault.example.com:8200

Step 1 — Use an .env File with docker run

The simplest approach is to store runtime secrets in a .env file on the host and pass it to the container at startup. The file never becomes part of the image.

# Create the env file — do NOT commit this to source control
cat > /etc/myapp/secrets.env <<'EOF'
DB_PASSWORD=s3cr3tP@ssword
API_KEY=abc123xyz
JWT_SECRET=supersecretjwtsigning
EOF

chmod 600 /etc/myapp/secrets.env

# Pass the file to docker run
docker run -d 
  --name myapp 
  --env-file /etc/myapp/secrets.env 
  myapp:latest

# Verify variables are visible inside the container
docker exec myapp printenv DB_PASSWORD

Step 2 — Use Environment Variables in docker-compose.yml

Docker Compose can read from an .env file in the project directory and interpolate values. Avoid hardcoding values directly in docker-compose.yml.

# .env file (same directory as docker-compose.yml — NOT committed)
cat > .env < docker-compose.yml <<'EOF'
version: "3.9"
services:
  app:
    image: myapp:latest
    environment:
      - DB_PASSWORD=${DB_PASSWORD}
      - API_KEY=${API_KEY}
    env_file:
      - .env
EOF

docker compose up -d

Step 3 — Create and Use Docker Swarm Secrets

Docker Swarm secrets are encrypted at rest and in transit, and mounted into containers as in-memory tmpfs files. They are never stored in environment variables or image layers.

# Create a secret from a file
echo "s3cr3tP@ssword" | docker secret create db_password -

# Or from a file
docker secret create tls_cert ./certs/server.crt

# List secrets
docker secret ls

# Use the secret in a Swarm service
docker service create 
  --name myapp 
  --secret db_password 
  --secret tls_cert 
  myapp:latest

# Inside the container, secrets appear at /run/secrets/
docker exec $(docker ps -q --filter name=myapp) cat /run/secrets/db_password

Step 4 — Use BuildKit –secret for Build-Time Secrets

Build-time secrets (npm tokens, pip credentials) should never appear in image layers. BuildKit’s --mount=type=secret exposes a secret only during a specific RUN instruction and does not persist it in the layer.

# Dockerfile — uses secret without baking it into the layer
cat > Dockerfile <<'EOF'
# syntax=docker/dockerfile:1
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./

# Secret mounted only during this RUN step, never in the image
RUN --mount=type=secret,id=npm_token 
    NPM_TOKEN=$(cat /run/secrets/npm_token) 
    npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN && 
    npm ci && 
    npm config delete //registry.npmjs.org/:_authToken
COPY . .
RUN npm run build
EOF

# Build passing the secret — never stored in the image
DOCKER_BUILDKIT=1 docker build 
  --secret id=npm_token,src=$HOME/.npm_token 
  -t myapp:latest .

# Confirm the token is NOT in the image history
docker history myapp:latest

Step 5 — Audit Image Layers for Leaked Secrets

Use docker history and image inspection to verify no secrets have been baked into image layers, which is a common mistake when using ENV or ARG to pass credentials during build.

# Show all layer commands — look for any ENV or ARG with secrets
docker history --no-trunc myapp:latest

# Inspect the full image config
docker inspect myapp:latest | python3 -m json.tool | grep -i -A2 'env|arg|pass|secret|key'

# Never do this in a Dockerfile — secrets leak into layer metadata:
# ARG DB_PASSWORD
# ENV DB_PASSWORD=$DB_PASSWORD

Step 6 — HashiCorp Vault Integration Overview

For production environments, HashiCorp Vault provides dynamic secrets, lease-based credentials, and fine-grained audit logging. Applications authenticate to Vault at runtime and fetch secrets on demand rather than receiving them from the orchestrator.

# Install the Vault CLI on RHEL 8
dnf install -y yum-utils
yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
dnf install -y vault

# Authenticate to Vault (example using token auth)
export VAULT_ADDR=http://vault.example.com:8200
vault login s.YourRootOrAppToken

# Write a secret
vault kv put secret/myapp db_password=s3cr3tP@ssword api_key=abc123

# Read from your app at runtime (e.g., in an entrypoint script)
vault kv get -field=db_password secret/myapp

# Use the Vault Agent sidecar to automatically inject secrets as files
# into the container filesystem — see Vault Agent Injector documentation

Conclusion

Secure secrets management requires choosing the right tool for each context: .env files for simple local development, Docker Swarm secrets for encrypted runtime delivery, BuildKit secret mounts for build-time credentials, and Vault for production-grade dynamic secrets. The golden rule is consistent regardless of method: secrets must never appear in image layers, environment variable dumps, or source repositories. Regular audits with docker history and image inspection catch accidental leaks before they reach a registry.

Next steps: How to Build Docker Images with Multi-Stage Builds on RHEL 8, How to Configure Docker Daemon TLS Encryption on RHEL 8, and How to Install and Use Skopeo for Container Image Management on RHEL 8.