ZORL
How to Set Up .env Validation in Your CI/CD Pipeline
6 min read

How to Set Up .env Validation in Your CI/CD Pipeline

Step-by-step guide to adding environment variable validation to GitHub Actions, GitLab CI, and other CI/CD systems. Catch configuration errors before they reach production.

ci/cdgithub actionsgitlab cienvironment validationdevops

You have tests for your code. You have linting for your syntax. But what about your configuration? A single missing environment variable can take down production just as effectively as a bug in your business logic.

This guide shows you how to add environment variable validation to your CI/CD pipeline so configuration errors get caught before deployment, not after.

Why Validate in CI/CD?

Consider this scenario: your app works perfectly in staging. You deploy to production. It crashes immediately because someone forgot to set STRIPE_WEBHOOK_SECRET in the production environment.

This happens more often than anyone wants to admit. The fix is simple: validate your environment configuration as part of your deployment pipeline.

Benefits of CI/CD validation:

  • Fail fast - Catch missing or invalid variables before deployment starts
  • Consistent checks - Same validation runs every time, no human error
  • Documentation - Your schema becomes the source of truth for required configuration
  • Audit trail - Git history shows when configuration requirements changed

The Setup: Schema-Based Validation

The approach is straightforward:

  1. Define a schema that describes your required environment variables
  2. Run a validation check in your CI/CD pipeline
  3. Block deployment if validation fails

Here is an example schema (env.schema.json):

{
  "NODE_ENV": {
    "type": "enum",
    "values": ["development", "staging", "production"],
    "required": true
  },
  "DATABASE_URL": {
    "type": "url",
    "required": true
  },
  "REDIS_URL": {
    "type": "url",
    "required": true
  },
  "API_SECRET_KEY": {
    "type": "string",
    "required": true,
    "validate": { "min_length": 32 }
  },
  "PORT": {
    "type": "int",
    "default": 3000,
    "validate": { "min": 1024, "max": 65535 }
  },
  "LOG_LEVEL": {
    "type": "enum",
    "values": ["debug", "info", "warn", "error"],
    "default": "info"
  }
}

This schema documents every environment variable your app needs, their types, and any constraints. Commit it to your repository alongside your code.

GitHub Actions Setup

Here is how to add validation to a GitHub Actions workflow:

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  validate-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Install zenv
        run: |
          curl -sSL https://github.com/zorl-engine/zorath-env/releases/latest/download/zenv-linux-x64 -o zenv
          chmod +x zenv
          sudo mv zenv /usr/local/bin/

      - name: Validate environment configuration
        env:
          NODE_ENV: production
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          REDIS_URL: ${{ secrets.REDIS_URL }}
          API_SECRET_KEY: ${{ secrets.API_SECRET_KEY }}
        run: |
          zenv check --schema env.schema.json

      - name: Run tests
        run: npm test

      - name: Deploy
        if: success()
        run: |
          # Your deployment command here
          echo "Deploying to production..."

The key is the zenv check step. If any required variable is missing or invalid, the command exits with code 1 and the workflow fails. Deployment never happens.

Using Environment Files

If your CI environment uses .env files:

- name: Validate environment configuration
  run: |
    # Create .env from secrets
    echo "NODE_ENV=production" >> .env
    echo "DATABASE_URL=${{ secrets.DATABASE_URL }}" >> .env
    echo "REDIS_URL=${{ secrets.REDIS_URL }}" >> .env
    echo "API_SECRET_KEY=${{ secrets.API_SECRET_KEY }}" >> .env

    # Validate
    zenv check

    # Clean up (don't leave secrets in artifacts)
    rm .env

GitLab CI Setup

For GitLab CI/CD:

# .gitlab-ci.yml
stages:
  - validate
  - test
  - deploy

variables:
  NODE_ENV: production

validate-config:
  stage: validate
  image: rust:latest
  before_script:
    - cargo install zorath-env
  script:
    - zenv check --schema env.schema.json
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

test:
  stage: test
  needs: [validate-config]
  script:
    - npm test

deploy:
  stage: deploy
  needs: [test]
  script:
    - echo "Deploying..."
  environment:
    name: production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

GitLab CI variables are automatically available as environment variables, so zenv check can validate them directly.

Jenkins Pipeline

For Jenkins declarative pipelines:

pipeline {
    agent any

    environment {
        NODE_ENV = 'production'
        DATABASE_URL = credentials('database-url')
        REDIS_URL = credentials('redis-url')
        API_SECRET_KEY = credentials('api-secret-key')
    }

    stages {
        stage('Validate Config') {
            steps {
                sh '''
                    curl -sSL https://github.com/zorl-engine/zorath-env/releases/latest/download/zenv-linux-x64 -o zenv
                    chmod +x zenv
                    ./zenv check --schema env.schema.json
                '''
            }
        }

        stage('Test') {
            steps {
                sh 'npm test'
            }
        }

        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh 'echo "Deploying..."'
            }
        }
    }
}

Docker and Container Deployments

For containerized applications, validate before the container starts:

# Dockerfile
FROM node:20-alpine

# Install zenv
RUN curl -sSL https://github.com/zorl-engine/zorath-env/releases/latest/download/zenv-linux-x64 -o /usr/local/bin/zenv \
    && chmod +x /usr/local/bin/zenv

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .

# Validate on container start
CMD ["sh", "-c", "zenv check && node server.js"]

Or in your docker-compose setup:

# docker-compose.yml
services:
  app:
    build: .
    env_file: .env.production
    command: sh -c "zenv check && node server.js"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

Handling Validation Failures

When validation fails, you want clear, actionable error messages. A good validation tool tells you exactly what is wrong:

zenv check failed:

- DATABASE_URL: missing (required)
- API_SECRET_KEY: too short (minimum 32 characters, got 16)
- LOG_LEVEL: invalid value "verbose" (expected one of: debug, info, warn, error)

This output should go directly to your CI logs. No guessing what went wrong.

Best Practices

1. Validate Early

Put validation as the first step in your pipeline, before tests or builds. Why waste 10 minutes running tests if the deploy would fail anyway?

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: zenv check

  test:
    needs: validate  # Only run if validation passes
    runs-on: ubuntu-latest
    steps:
      - run: npm test

2. Keep Schema in Version Control

Your env.schema.json should be committed alongside your code. This way:

  • Changes to configuration requirements go through code review
  • You have a history of when requirements changed
  • New team members can see exactly what configuration is needed

3. Use Different Schemas per Environment

If development and production have different requirements:

env.schema.json          # Base schema
env.schema.dev.json      # Development overrides
env.schema.prod.json     # Production requirements
- name: Validate (Production)
  if: github.ref == 'refs/heads/main'
  run: zenv check --schema env.schema.prod.json

- name: Validate (Development)
  if: github.ref != 'refs/heads/main'
  run: zenv check --schema env.schema.dev.json

4. Document with Examples

Include an .env.example file with placeholder values:

# .env.example
NODE_ENV=development
DATABASE_URL=postgres://localhost/myapp
REDIS_URL=redis://localhost:6379
API_SECRET_KEY=your-secret-key-here-min-32-chars
PORT=3000
LOG_LEVEL=debug

This helps developers set up their local environment and shows what variables are needed.

Common Pitfalls

Pitfall 1: Hardcoding secrets in CI config

Never put actual secret values in your CI configuration files. Use your CI platform's secrets management:

# Bad - secret in plain text
env:
  API_KEY: sk_live_xxxxx

# Good - reference to secret store
env:
  API_KEY: ${{ secrets.API_KEY }}

Pitfall 2: Skipping validation for hotfixes

It is tempting to bypass validation for urgent fixes. Do not. A broken config in a hotfix creates a second outage.

Pitfall 3: Not validating in staging

Validate in staging too, not just production. This catches issues before they ever reach prod.

Getting Started

If you are not validating environment configuration today, start simple:

  1. Create an env.schema.json listing your required variables
  2. Add a validation step to your CI pipeline
  3. Run it on your next deployment

It takes 15 minutes to set up and will save you from the 3 AM "why is production down" call. Worth it.

Resources

Share this article

Z

ZORL Team

Building developer tools that make configuration easier. Creators of zorath-env.

Previous
The Hidden Cost of Configuration Drift
Next
Environment Variables vs Config Files: When to Use Each

Related Articles

Never miss config bugs again

Use zorath-env to validate your environment variables before they cause production issues.