---
path: onboarding/devops-onboarding/cicd-patterns.mdx
title: CI/CD integration patterns
sidebar_position: 6
slug: cicd-patterns
pagination_next: null
pagination_prev: null
description: >-
  Passwork integration patterns for CI/CD platforms: GitLab CI, GitHub Actions,
  Bitbucket Pipelines, Kubernetes init containers and sidecars, design principles,
  and security best practices.
keywords:
  - Passwork
  - CI/CD
  - GitLab CI
  - GitHub Actions
  - Bitbucket Pipelines
  - Kubernetes
  - Docker
  - secret injection
  - passwork-cli
  - pipeline
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

The standard pattern for Passwork CI/CD integration:

```
Pipeline runner → passwork-cli exec → deploy/build script with secrets in ENV
```

Only three Passwork credentials live in the CI/CD platform's own secret storage: `PASSWORK_HOST`, `PASSWORK_TOKEN`, and `PASSWORK_MASTER_KEY`. Everything else — database passwords, API keys, cloud credentials — lives in Passwork and is injected at runtime.

---

## Design principles

### One service account per pipeline type

A production deploy pipeline and a staging deploy pipeline should use different service accounts with access to their respective environments. This limits blast radius if a token is leaked.

### Read only as the default access level

Grant `Read only` to all service accounts unless the pipeline explicitly rotates credentials, in which case `Read and edit` is needed.

### Never echo secrets

CI/CD platforms capture stdout and stderr in job logs. Never `echo $SECRET` or `printenv | grep SECRET` in pipeline steps. Use secrets only as arguments to commands.

### Use folder ID, not item IDs

Structure your vault so a pipeline can use `--folder-id` to get all its secrets from one place. Hardcoding individual `--password-id` values is only justified when you need a single specific credential.

---

## GitLab CI

### Setup

In **Settings → CI/CD → Variables**, add (all set to **Protected** and **Masked** where applicable):

| Variable | Type | Masked |
|----------|------|--------|
| `PASSWORK_HOST` | Variable | No |
| `PASSWORK_TOKEN` | Variable | Yes |
| `PASSWORK_MASTER_KEY` | Variable | Yes |
| `PROD_SECRETS_FOLDER_ID` | Variable | No |
| `STAGING_SECRETS_FOLDER_ID` | Variable | No |

### Basic pipeline

```yaml
stages:
  - deploy

deploy_production:
  stage: deploy
  image: passwork/passwork-cli:latest
  variables:
    PASSWORK_HOST: $PASSWORK_HOST
    PASSWORK_TOKEN: $PASSWORK_TOKEN
    PASSWORK_MASTER_KEY: $PASSWORK_MASTER_KEY
  script:
    - passwork-cli exec --folder-id "$PROD_SECRETS_FOLDER_ID" ./deploy.sh
  environment:
    name: production
  when: manual
  only:
    - main
```

### Multi-environment pipeline

```yaml
.deploy_template: &deploy_template
  image: passwork/passwork-cli:latest
  variables:
    PASSWORK_HOST: $PASSWORK_HOST
    PASSWORK_TOKEN: $PASSWORK_TOKEN
    PASSWORK_MASTER_KEY: $PASSWORK_MASTER_KEY
  script:
    - passwork-cli exec --folder-id "$SECRETS_FOLDER_ID" ./deploy.sh

deploy_staging:
  <<: *deploy_template
  stage: deploy
  variables:
    SECRETS_FOLDER_ID: $STAGING_SECRETS_FOLDER_ID
  environment:
    name: staging
  only:
    - develop

deploy_production:
  <<: *deploy_template
  stage: deploy
  variables:
    SECRETS_FOLDER_ID: $PROD_SECRETS_FOLDER_ID
  environment:
    name: production
  when: manual
  only:
    - main
```

---

## GitHub Actions

### Setup

In **Settings → Secrets and variables → Actions**:

- **Secrets** (encrypted): `PASSWORK_HOST`, `PASSWORK_TOKEN`, `PASSWORK_MASTER_KEY`
- **Variables** (plain): `PROD_SECRETS_FOLDER_ID`, `STAGING_SECRETS_FOLDER_ID`

### Basic workflow

```yaml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy with secrets from Passwork
        run: |
          docker run --rm \
            -e PASSWORK_HOST="${{ secrets.PASSWORK_HOST }}" \
            -e PASSWORK_TOKEN="${{ secrets.PASSWORK_TOKEN }}" \
            -e PASSWORK_MASTER_KEY="${{ secrets.PASSWORK_MASTER_KEY }}" \
            -v ${{ github.workspace }}:/app \
            -w /app \
            passwork/passwork-cli:latest \
            exec --folder-id "${{ vars.PROD_SECRETS_FOLDER_ID }}" ./deploy.sh
```

### Multi-environment workflow

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
    steps:
      - uses: actions/checkout@v4

      - name: Deploy
        run: |
          docker run --rm \
            -e PASSWORK_HOST="${{ secrets.PASSWORK_HOST }}" \
            -e PASSWORK_TOKEN="${{ secrets.PASSWORK_TOKEN }}" \
            -e PASSWORK_MASTER_KEY="${{ secrets.PASSWORK_MASTER_KEY }}" \
            -v ${{ github.workspace }}:/app \
            -w /app \
            passwork/passwork-cli:latest \
            exec --folder-id "${{ vars.SECRETS_FOLDER_ID }}" ./deploy.sh
```

---

## Bitbucket Pipelines

```yaml
image: passwork/passwork-cli:latest

pipelines:
  branches:
    main:
      - step:
          name: Deploy to production
          deployment: production
          script:
            - passwork-cli exec --folder-id "$PROD_SECRETS_FOLDER_ID" ./deploy.sh
```

Configure `PASSWORK_HOST`, `PASSWORK_TOKEN`, `PASSWORK_MASTER_KEY`, and folder IDs in **Repository settings → Pipelines → Repository variables** (mark the tokens as **Secured**).

---

## Kubernetes

### Init container pattern

An init container fetches secrets before the main application starts and writes them to a shared in-memory volume. The main container reads from that volume.

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: order-service
spec:
  initContainers:
    - name: fetch-secrets
      image: passwork/passwork-cli:latest
      env:
        - name: PASSWORK_HOST
          valueFrom:
            secretKeyRef:
              name: passwork-credentials
              key: host
        - name: PASSWORK_TOKEN
          valueFrom:
            secretKeyRef:
              name: passwork-credentials
              key: token
        - name: PASSWORK_MASTER_KEY
          valueFrom:
            secretKeyRef:
              name: passwork-credentials
              key: master-key
      command:
        - sh
        - -c
        - passwork-cli exec --folder-id "$SECRETS_FOLDER_ID" env > /secrets/.env
      volumeMounts:
        - name: secrets-volume
          mountPath: /secrets

  containers:
    - name: app
      image: order-service:latest
      command:
        - sh
        - -c
        - |
          set -a && source /secrets/.env && set +a
          exec ./app
      volumeMounts:
        - name: secrets-volume
          mountPath: /secrets
          readOnly: true

  volumes:
    - name: secrets-volume
      emptyDir:
        medium: Memory   # in-memory only — not written to disk
```

The Kubernetes Secret `passwork-credentials` holds only the Passwork bootstrap tokens.

### Sidecar pattern for periodic refresh

When secrets rotate frequently, a sidecar container refreshes the .env file without restarting the pod:

```yaml
  containers:
    - name: app
      image: order-service:latest
      # app reloads /secrets/.env when it detects a change (via inotify or polling)

    - name: secrets-sync
      image: passwork/passwork-cli:latest
      env:
        - name: PASSWORK_HOST
          valueFrom:
            secretKeyRef:
              name: passwork-credentials
              key: host
        - name: PASSWORK_TOKEN
          valueFrom:
            secretKeyRef:
              name: passwork-credentials
              key: token
        - name: PASSWORK_MASTER_KEY
          valueFrom:
            secretKeyRef:
              name: passwork-credentials
              key: master-key
      command:
        - sh
        - -c
        - |
          while true; do
            passwork-cli exec --folder-id "$SECRETS_FOLDER_ID" env > /secrets/.env.new
            mv /secrets/.env.new /secrets/.env
            sleep 300
          done
      volumeMounts:
        - name: secrets-volume
          mountPath: /secrets
```

---

## Audit trail

Every secret access by a service account is recorded in Passwork's **action history** under the service account's identity. This gives you a complete, centralized audit trail:

- Which pipeline fetched which credentials and when
- Which rotation script updated which item
- Any access outside expected patterns (alert candidates)

Navigate to **Settings → Activity log** and filter by the service account username to review its activity.

For full CI/CD integration reference, see [CI/CD and infrastructure integrations](https://passwork.pro/tech-guides/secret-management/integrations/).
