KeyEnvKeyEnv

GitLab CI/CD

Integrate KeyEnv with GitLab CI/CD pipelines.

GitLab CI/CD Integration

This guide shows how to use KeyEnv secrets in your GitLab CI/CD pipelines.

Overview

There are two approaches to integrate KeyEnv with GitLab CI/CD:

MethodBest ForDependencies
CLI-basedFull featuresKeyEnv CLI
curl-basedMinimal imagescurl, jq

Both approaches use a service token stored as a GitLab CI/CD variable.

Prerequisites

  1. A KeyEnv project with secrets configured
  2. A service token with access to the appropriate environment
  3. A GitLab repository with CI/CD enabled

Creating a Service Token

  1. Go to your KeyEnv dashboard
  2. Navigate to Project Settings > Service Tokens
  3. Click Create Token
  4. Name it "GitLab CI - Production" (or the appropriate environment)
  5. Select the environment (e.g., production)
  6. Copy the token

Adding the Token to GitLab

  1. Go to your GitLab repository
  2. Navigate to Settings > CI/CD > Variables
  3. Click Add variable
  4. Configure the variable:
    • Key: KEYENV_TOKEN
    • Value: Your service token
    • Type: Variable
    • Flags: Check Mask variable and optionally Protect variable
  5. Click Add variable

Use Protected variables to limit token access to protected branches only.


Option 1: CLI-Based Integration

Install the KeyEnv CLI during your pipeline and use it to fetch secrets.

Basic Example

# .gitlab-ci.yml
stages:
  - deploy

deploy:
  stage: deploy
  image: ubuntu:latest
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  before_script:
    - apt-get update && apt-get install -y curl
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"
  script:
    - keyenv pull -e production
    - source .env
    - ./deploy.sh
  only:
    - main

Using keyenv run

Inject secrets directly into your command without writing to disk:

# .gitlab-ci.yml
deploy:
  stage: deploy
  image: ubuntu:latest
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  before_script:
    - apt-get update && apt-get install -y curl
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"
  script:
    - keyenv run -e production -- ./deploy.sh
  only:
    - main

Reusable Template

Create a reusable template for multiple jobs:

# .gitlab-ci.yml
.keyenv_setup:
  before_script:
    - apt-get update && apt-get install -y curl
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"

stages:
  - test
  - deploy

test:
  extends: .keyenv_setup
  stage: test
  image: ubuntu:latest
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  script:
    - keyenv pull -e development
    - npm ci
    - npm test

deploy_staging:
  extends: .keyenv_setup
  stage: deploy
  image: ubuntu:latest
  environment:
    name: staging
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  script:
    - keyenv run -e staging -- ./deploy.sh
  only:
    - develop

deploy_production:
  extends: .keyenv_setup
  stage: deploy
  image: ubuntu:latest
  environment:
    name: production
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  script:
    - keyenv run -e production -- ./deploy.sh
  only:
    - main

Dynamic Environment Selection

Use GitLab's $CI_ENVIRONMENT_NAME variable for automatic environment mapping:

# .gitlab-ci.yml
deploy:
  stage: deploy
  image: ubuntu:latest
  environment:
    name: $CI_COMMIT_REF_NAME
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  before_script:
    - apt-get update && apt-get install -y curl
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"
  script:
    - keyenv pull -e $CI_ENVIRONMENT_NAME
    - ./deploy.sh

Option 2: curl-Based Integration

Fetch secrets via the API without installing the CLI. Useful for minimal Docker images.

Basic Example

# .gitlab-ci.yml
variables:
  KEYENV_TOKEN: $KEYENV_TOKEN
  KEYENV_PROJECT: "YOUR_PROJECT_ID"
  KEYENV_ENV: "production"

deploy:
  image: alpine:latest
  before_script:
    - apk add --no-cache curl jq
  script:
    - |
      # Fetch secrets from KeyEnv API
      SECRETS=$(curl -sf -H "Authorization: Bearer $KEYENV_TOKEN" \
        "https://api.keyenv.dev/api/v1/projects/$KEYENV_PROJECT/environments/$KEYENV_ENV/secrets/export")

      # Export each secret as an environment variable
      for row in $(echo "$SECRETS" | jq -c '.secrets[]'); do
        key=$(echo $row | jq -r '.key')
        value=$(echo $row | jq -r '.value')
        export "$key=$value"
      done

      # Run your commands with secrets available
      ./deploy.sh
  only:
    - main

Reusable Template

# .gitlab-ci.yml
.keyenv_curl:
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
    KEYENV_PROJECT: "YOUR_PROJECT_ID"
  before_script:
    - apk add --no-cache curl jq
    - |
      SECRETS=$(curl -sf -H "Authorization: Bearer $KEYENV_TOKEN" \
        "https://api.keyenv.dev/api/v1/projects/$KEYENV_PROJECT/environments/$KEYENV_ENV/secrets/export")
      for row in $(echo "$SECRETS" | jq -c '.secrets[]'); do
        key=$(echo $row | jq -r '.key')
        value=$(echo $row | jq -r '.value')
        export "$key=$value"
      done

deploy_staging:
  extends: .keyenv_curl
  image: alpine:latest
  variables:
    KEYENV_ENV: "staging"
  script:
    - ./deploy.sh
  only:
    - develop

deploy_production:
  extends: .keyenv_curl
  image: alpine:latest
  variables:
    KEYENV_ENV: "production"
  script:
    - ./deploy.sh
  only:
    - main

Writing to a .env File

If your application needs a .env file:

# .gitlab-ci.yml
deploy:
  image: alpine:latest
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
    KEYENV_PROJECT: "YOUR_PROJECT_ID"
  before_script:
    - apk add --no-cache curl jq
    - |
      curl -sf -H "Authorization: Bearer $KEYENV_TOKEN" \
        "https://api.keyenv.dev/api/v1/projects/$KEYENV_PROJECT/environments/production/secrets/export" \
        | jq -r '.secrets[] | "\(.key)=\(.value)"' > .env
  script:
    - source .env
    - ./deploy.sh
  only:
    - main

Docker Workflows

Docker Build with Secrets

# .gitlab-ci.yml
build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  before_script:
    - apk add --no-cache curl bash
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"
  script:
    - keyenv pull -e production -o .env.production
    - docker build --secret id=env,src=.env.production -t myapp:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  only:
    - main

Docker Compose Deployment

# .gitlab-ci.yml
deploy:
  stage: deploy
  image: docker:latest
  services:
    - docker:dind
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  before_script:
    - apk add --no-cache curl bash docker-compose
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"
  script:
    - keyenv pull -e production
    - docker-compose up -d
  only:
    - main

Language-Specific Examples

Node.js Application

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

test:
  stage: test
  image: node:20-alpine
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  before_script:
    - apk add --no-cache curl bash
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"
    - keyenv pull -e development
  script:
    - npm ci
    - npm test
    - npm run build

deploy:
  stage: deploy
  image: node:20-alpine
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  before_script:
    - apk add --no-cache curl bash
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"
  script:
    - keyenv run -e production -- npm run deploy
  only:
    - main

Python Application

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

test:
  stage: test
  image: python:3.12-slim
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  before_script:
    - apt-get update && apt-get install -y curl
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"
    - keyenv pull -e development
  script:
    - pip install -r requirements.txt
    - pytest

deploy:
  stage: deploy
  image: python:3.12-slim
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  before_script:
    - apt-get update && apt-get install -y curl
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"
  script:
    - keyenv run -e production -- python deploy.py
  only:
    - main

Go Application

# .gitlab-ci.yml
stages:
  - test
  - build

test:
  stage: test
  image: golang:1.22-alpine
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  before_script:
    - apk add --no-cache curl bash
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"
    - keyenv pull -e development
  script:
    - go test ./...

build:
  stage: build
  image: golang:1.22-alpine
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  before_script:
    - apk add --no-cache curl bash
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"
  script:
    - keyenv run -e production -- go build -o app .
  artifacts:
    paths:
      - app
  only:
    - main

Security Best Practices

  1. Use protected variables - Mark KEYENV_TOKEN as protected to limit access to protected branches
  2. Use masked variables - Always mask the token to prevent exposure in logs
  3. Scope tokens narrowly - Create separate tokens for each environment
  4. Don't log secrets - Avoid echo $SECRET in your scripts
  5. Use keyenv run - Prefer runtime injection over writing to disk
  6. Limit job access - Use only and except to control which jobs run

Never commit service tokens to your repository. Always use GitLab CI/CD variables.


Troubleshooting

"keyenv: command not found"

Ensure the PATH is updated after installation:

before_script:
  - curl -fsSL https://keyenv.dev/install.sh | bash
  - export PATH="$HOME/.keyenv/bin:$PATH"  # Required!

"Authentication failed"

  • Verify your token is correct in GitLab CI/CD variables
  • Check the token hasn't expired
  • Ensure the variable is not protected if running on unprotected branches

"Access denied"

  • The token may not have access to the specified project
  • The token may not have access to the specified environment
  • Check token permissions in KeyEnv dashboard

"curl: command not found"

Install curl in your Docker image:

# For Alpine
before_script:
  - apk add --no-cache curl

# For Debian/Ubuntu
before_script:
  - apt-get update && apt-get install -y curl

"jq: command not found" (curl-based approach)

Install jq in your Docker image:

# For Alpine
before_script:
  - apk add --no-cache curl jq

# For Debian/Ubuntu
before_script:
  - apt-get update && apt-get install -y curl jq

Secrets not available in subsequent steps

Environment variables set with export in before_script are available in script but not in after_script or other jobs. For sharing between jobs, use artifacts or GitLab CI/CD variables.


Self-Hosted KeyEnv

If you're running a self-hosted KeyEnv instance:

# .gitlab-ci.yml
variables:
  KEYENV_API_URL: https://keyenv.your-company.com

deploy:
  variables:
    KEYENV_TOKEN: $KEYENV_TOKEN
  script:
    - keyenv pull -e production
    - ./deploy.sh

For curl-based integration, update the API URL:

# .gitlab-ci.yml
variables:
  KEYENV_API_URL: https://keyenv.your-company.com
  KEYENV_PROJECT: "YOUR_PROJECT_ID"

deploy:
  before_script:
    - apk add --no-cache curl jq
    - |
      SECRETS=$(curl -sf -H "Authorization: Bearer $KEYENV_TOKEN" \
        "$KEYENV_API_URL/api/v1/projects/$KEYENV_PROJECT/environments/$KEYENV_ENV/secrets/export")
      # ... rest of script

Complete Example

Here's a complete .gitlab-ci.yml for a typical Node.js application:

# .gitlab-ci.yml

# Define stages
stages:
  - test
  - build
  - deploy

# Reusable KeyEnv setup
.keyenv_setup:
  before_script:
    - apk add --no-cache curl bash
    - curl -fsSL https://keyenv.dev/install.sh | bash
    - export PATH="$HOME/.keyenv/bin:$PATH"

# Variables (set KEYENV_TOKEN in GitLab CI/CD settings)
variables:
  KEYENV_TOKEN: $KEYENV_TOKEN

# Test job
test:
  extends: .keyenv_setup
  stage: test
  image: node:20-alpine
  script:
    - keyenv pull -e development
    - npm ci
    - npm test
  coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'

# Build job
build:
  extends: .keyenv_setup
  stage: build
  image: node:20-alpine
  script:
    - keyenv pull -e staging
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour
  only:
    - develop
    - main

# Deploy to staging
deploy_staging:
  extends: .keyenv_setup
  stage: deploy
  image: node:20-alpine
  environment:
    name: staging
    url: https://staging.example.com
  script:
    - keyenv run -e staging -- npm run deploy:staging
  only:
    - develop

# Deploy to production
deploy_production:
  extends: .keyenv_setup
  stage: deploy
  image: node:20-alpine
  environment:
    name: production
    url: https://example.com
  script:
    - keyenv run -e production -- npm run deploy:production
  only:
    - main
  when: manual

GitLab CI/CD Variables

Add these variables in Settings > CI/CD > Variables:

VariableValueProtectedMasked
KEYENV_TOKENsrv_your_token_hereYes (optional)Yes

KeyEnv Environments

Create environments in KeyEnv matching your deployment stages:

EnvironmentPurpose
developmentTesting with test data
stagingPre-production validation
productionLive production deployment

On this page