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:
| Method | Best For | Dependencies |
|---|---|---|
| CLI-based | Full features | KeyEnv CLI |
| curl-based | Minimal images | curl, jq |
Both approaches use a service token stored as a GitLab CI/CD variable.
Prerequisites
- A KeyEnv project with secrets configured
- A service token with access to the appropriate environment
- A GitLab repository with CI/CD enabled
Creating a Service Token
- Go to your KeyEnv dashboard
- Navigate to Project Settings > Service Tokens
- Click Create Token
- Name it "GitLab CI - Production" (or the appropriate environment)
- Select the environment (e.g.,
production) - Copy the token
Adding the Token to GitLab
- Go to your GitLab repository
- Navigate to Settings > CI/CD > Variables
- Click Add variable
- Configure the variable:
- Key:
KEYENV_TOKEN - Value: Your service token
- Type: Variable
- Flags: Check Mask variable and optionally Protect variable
- Key:
- 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:
- mainUsing 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:
- mainReusable 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:
- mainDynamic 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.shOption 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:
- mainReusable 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:
- mainWriting 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:
- mainDocker 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:
- mainDocker 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:
- mainLanguage-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:
- mainPython 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:
- mainGo 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:
- mainSecurity Best Practices
- Use protected variables - Mark
KEYENV_TOKENas protected to limit access to protected branches - Use masked variables - Always mask the token to prevent exposure in logs
- Scope tokens narrowly - Create separate tokens for each environment
- Don't log secrets - Avoid
echo $SECRETin your scripts - Use
keyenv run- Prefer runtime injection over writing to disk - Limit job access - Use
onlyandexceptto 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 jqSecrets 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.shFor 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 scriptComplete 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: manualGitLab CI/CD Variables
Add these variables in Settings > CI/CD > Variables:
| Variable | Value | Protected | Masked |
|---|---|---|---|
KEYENV_TOKEN | srv_your_token_here | Yes (optional) | Yes |
KeyEnv Environments
Create environments in KeyEnv matching your deployment stages:
| Environment | Purpose |
|---|---|
development | Testing with test data |
staging | Pre-production validation |
production | Live production deployment |