Skip to main content

Managing secrets with private Docker images

Log in to add to favourites

Page last updated 06 February 2026

What are environment variables?

Environment variables are key-value pairs provided by the operating system that influence how applications run. They’re often used to configure paths, enable features, or inject sensitive information like API keys or tokens without hardcoding them into source code.

For example, the following commands print the PATH variable in different environments:

Shell
echo $PATH        # macOS/Linux
echo $env:PATH    # PowerShell
echo %PATH%       # Windows CMD
node -e "console.log(process.env.PATH)"  # Node.js

How environment variables are used in development

In the JavaScript ecosystem, environment variables are often defined in a file named .env (or some variation) stored in the project root. This file contains values that are loaded at runtime and made available via process.env in Node.js:

yaml
# .env
PRIVATE_API_KEY=abc123
Shell
node -e "console.log(process.env.PRIVATE_API_KEY)"

These variables aren’t available by default - they need to be loaded explicitly in code (or implicitly through build tooling) using packages like dotenv.

Even for client-side web apps, Node.js is commonly used during the build step. Many frameworks (such as Next.js, Vite, or Webpack) allow you to expose specific variables to the frontend by prefixing them or using special loaders, plugins, imports, or comments. This means it's possible-intentionally or accidentally-to embed sensitive variables into client-side bundles.

What is a secret?

Environment variables themselves are not always sensitive. Many are harmless configuration flags or tokens used in low-risk contexts.

However, some variables-like API keys, credentials, or private tokens-must be treated as secrets. These values must never be committed to source control or exposed to the public.

For example, in Contensis:

  • Access Token - used to fetch public content from the Delivery API; safe and often exposed in frontend code.
  • 🛑 Role-based API Key (Client ID + Secret) - used to manage content; must be kept private and should never be exposed to the client.

The distinction is simple: if exposing a variable can result in unauthorized access or damage, it’s a secret.

Secrets in CI/CD pipelines

In a CI/CD environment like GitHub Actions, secrets are used to safely pass sensitive values into your builds and deployments without storing them in code.

GitHub provides a secure Secrets section in repository settings where you can define these variables. Once stored, their values:

  • Can be injected as environment variables in workflows.
  • Are write-only - you won’t be able to read them later, only update them.
  • Are automatically redacted from CI/CD pipeline logs.

Use this mechanism to store secrets like:

  • Private registry credentials
  • API keys
  • Authentication tokens

Bridging local .env Files and CI/CD secrets

During local development, developers often rely on .env files to load secrets as configuration variables. However, in CI/CD pipelines, these secrets must come from the CI provider’s secret management system (such as GitHub Secrets).

Our aim is to recreate the same environment securely in your pipeline - by injecting secrets into the build process without ever hardcoding them or exposing them in client-facing code.

Using GitHub secrets in CI builds and Docker images

Once your secrets are safely added to your repository, you can inject them into your CI workflows as environment variables or Docker build arguments, depending on how your application expects them.

Adding GitHub secrets to the repository

GitHub provides a secure way to manage sensitive values in each repository under Settings Secrets and variablesActions.

Add all required secrets here before continuing.

How to find repository secrets in GitHub repository settings
Example showing a new secret being created in GitHub secrets

Injecting secrets via .env files in CI

If your application already relies on a .env file (or some variation), you can generate this file during your CI workflow using the secrets stored in GitHub.

Create a .env with secrets:

yaml
- name: Generate local .env file containing secrets
  run: |
    cat <<EOF > .env
    PRIVATE_ENV_VAR=${{ secrets.PRIVATE_ENV_VAR }}
    PRIVATE_CLIENT_ID=${{ secrets.PRIVATE_CLIENT_ID }}
    PRIVATE_CLIENT_SECRET=${{ secrets.PRIVATE_CLIENT_SECRET }}
    EOF

Or append secrets to an existing .env file:

yaml
- name: Append secrets to local .env file
  run: |
    echo "PRIVATE_ENV_VAR=${{ secrets.PRIVATE_ENV_VAR }}" >> .env
    echo "PRIVATE_CLIENT_ID=${{ secrets.PRIVATE_CLIENT_ID }}" >> .env
    echo "PRIVATE_CLIENT_SECRET=${{ secrets.PRIVATE_CLIENT_SECRET }}" >> .env

Once generated, the .env file will be available to any build step that runs in the same job. It will be discarded automatically once the CI job completes.

Passing secrets to Docker as build arguments

If your application needs secrets to be available at Docker build time (to supply in RUN steps for example), you can pass them using the --build-arg flag.

Update your Dockerfile to define build arguments:

dockerfile
ARG PRIVATE_AUTH_SECRET
ARG PRIVATE_CLIENT_ID
ARG PRIVATE_CLIENT_SECRET

ENV PRIVATE_AUTH_SECRET=$PRIVATE_AUTH_SECRET
ENV PRIVATE_CLIENT_ID=$PRIVATE_CLIENT_ID
ENV PRIVATE_CLIENT_SECRET=$PRIVATE_CLIENT_SECRET

Update your GitHub Actions workflow to pass those secrets:

yaml
- name: Build container image
  env:
    APP_BUILD_IMAGE: ${{ env.APP_IMAGE }}:build-${{ github.run_number }}
    APP_LATEST_IMAGE: ${{ env.APP_IMAGE }}:latest
  run: |
    docker build \
      --build-arg PRIVATE_AUTH_SECRET=${{ secrets.PRIVATE_AUTH_SECRET }} \
      --build-arg PRIVATE_CLIENT_ID=${{ secrets.PRIVATE_CLIENT_ID }} \
      --build-arg PRIVATE_CLIENT_SECRET=${{ secrets.PRIVATE_CLIENT_SECRET }} \
      -t ${{ env.APP_BUILD_IMAGE }} \
      -t ${{ env.APP_LATEST_IMAGE }} .

Next Steps: Push to Registry and Deploy

After building the container image, you should push it to your container registry. Ensure that your registry is not publicly accessible, as public images can be pulled by anyone.

In GitHub, container images appear under Packages in your repository and are labelled as either Public or Private.

Deploying to Contensis Blocks

If you're deploying to Contensis Blocks, you'll need to:

You can include the push step as part of your existing build job or as a separate deployment job.

Developer Tips

  • Never include private secrets in code that is served to the client - for example, in components that are rendered on both the server and client side
  • Restrict the use of private secrets to server-side operations only
  • Never commit private secrets to source control, unless explicitly agreed upon for a specific, secured use case.
  • Be aware that build tools may inline process.env.* or special global variables during build time, which can expose secret values in final client-side bundles if used incorrectly.
  • Avoid pushing container images to public registries if they include private secrets, as this creates a significant risk of data leakage
  • Understand that leaks can happen - if a secret is exposed, immediately revoke any public access, inform your team, and rotate any affected keys or credentials as a top priority.

Still need help?

If you still need help after reading this article, don't hesitate to reach out to the Contensis community on Slack or raise a support ticket to get help from our team.
New support request