---
layout: 'page'
uri: '/deployment/ci-cd-integration'
position: 3
slug: 'deployment-ci-cd-integration'
parent: 'deployment'
navTitle: 'CI/CD integration'
title: 'CI/CD integration'
description: 'Validate and deploy documentation automatically in your CI/CD pipeline.'
---

# CI/CD integration

The same [Dockerfile](/deployment/docker-setup) you use locally serves two roles in CI/CD:

1. **Lint job** — validates docs on every pull/merge request. Catches broken links, invalid frontmatter, and other issues before review.
2. **Deploy job** — builds, pushes, and deploys the production image on merge to main branch.

The key insight: `docker build` runs `lint`, `import`, and `vectorize` at build time. If any validation fails, the build fails — broken docs never reach production.


## Prerequisites

- Docker with BuildKit enabled (default in Docker 23+)
- The [Dockerfile](/deployment/docker-setup) at `docker/documan/Dockerfile`
- CI secrets configured (see [Setting up secrets](#setting-up-secrets))

Do not use `SKIP_BUILD_STEPS=true` in CI — that flag is only for the initial local setup when docs may not have valid frontmatter yet.


## GitHub Actions

```yaml
name: Documan

on:
  pull_request:
  push:
    branches: [main]

jobs:
  lint:
    name: Lint documentation
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4

      - name: Validate docs
        run: |
          docker build \
            -f docker/documan/Dockerfile \
            -t documan-lint:latest .

  deploy:
    name: Build and push
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - name: Log in to registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        run: |
          docker build \
            -f docker/documan/Dockerfile \
            --secret id=DOCUMAN_LICENSE_KEY,env=DOCUMAN_LICENSE_KEY \
            --secret id=DOCUMAN_OPENAI_API_KEY,env=DOCUMAN_OPENAI_API_KEY \
            -t ghcr.io/${{ github.repository }}/documan:latest .
          docker push ghcr.io/${{ github.repository }}/documan:latest
        env:
          DOCUMAN_LICENSE_KEY: ${{ secrets.DOCUMAN_LICENSE_KEY }}
          DOCUMAN_OPENAI_API_KEY: ${{ secrets.DOCUMAN_OPENAI_API_KEY }}
```


## GitLab CI

```yaml
lint-documan:
  stage: test
  script:
    - |
      docker build \
        -f docker/documan/Dockerfile \
        -t documan-lint:latest .
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

build-documan:
  stage: build
  variables:
    IMAGE_NAME: "$CI_REGISTRY_IMAGE/documan"
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    - |
      docker build \
        -f docker/documan/Dockerfile \
        --secret id=DOCUMAN_LICENSE_KEY,env=DOCUMAN_LICENSE_KEY \
        --secret id=DOCUMAN_OPENAI_API_KEY,env=DOCUMAN_OPENAI_API_KEY \
        -t "${IMAGE_NAME}:latest" .
      docker push "${IMAGE_NAME}:latest"
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
```


## How it works

### Lint job (pull/merge requests)

Runs `docker build` which triggers `lint` and `import` inside the Dockerfile. No secrets are needed — `vectorize` is automatically skipped when `DOCUMAN_OPENAI_API_KEY` is not provided, and `import` works without `DOCUMAN_LICENSE_KEY` for projects with up to 100 files.

If your project has more than 100 files, add `DOCUMAN_LICENSE_KEY` to the lint job as well.

### Deploy job (merge to main)

Runs the full build with secrets — `lint`, `import`, and `vectorize`. Pushes the image to your container registry. Your infrastructure pulls the latest image and restarts the service.


## Setting up secrets

Both secrets are optional. `DOCUMAN_LICENSE_KEY` is only needed for projects with more than 100 files. `DOCUMAN_OPENAI_API_KEY` enables semantic search — without it, vectorize is skipped.

### GitHub Actions

Go to your repository **Settings > Secrets and variables > Actions** and add:

- `DOCUMAN_LICENSE_KEY` — your license key (required for >100 files)
- `DOCUMAN_OPENAI_API_KEY` — your OpenAI API key (enables semantic search)

### GitLab CI

Go to your project **Settings > CI/CD > Variables** and add the same variables. Mark them as **masked** and **protected**.

---

[← Docker Compose](/deployment/docker-compose-setup.md) | [🤖 AI integration →](/ai-integration.md)