Introduction

OverviewPhilosophyStructureUpdatesFAQ

Usage

Other

The standard
Deployment

GitLab CI/CD

The pipeline that verifies, builds and ships a kreogen project.

Generated projects ship a .gitlab-ci.yml that verifies every merge request and builds images from the default branch.

Stages

verify — lint, typecheck and test, all in parallel. This is the same bun run verify you run locally.

build — one image each for app, web and api, built in a matrix, on the default branch only.

This describes the pipeline a generated project gets, from templates/project.gitlab-ci.yml. kreogen's own pipeline is larger — five stages, including package and release — because the template also publishes a CLI to npm and cuts releases. A client project ships images, not a package.

Caching

Two independent caches:

  • Dependencies, keyed on bun.lock. Reinstalling per job against a warm cache takes a couple of seconds and is far more reliable than passing node_modules between jobs as artifacts.
  • Turbo, keyed on the branch with a fallback to the default branch, so a new branch inherits main's task cache on its first run.

Both live inside $CI_PROJECT_DIR, because GitLab cannot cache paths outside it.

Images

Built with buildx against a registry-backed cache, tagged sha-<short> and latest, and pushed to the project's container registry using CI_JOB_TOKEN — no extra credentials to configure.

Deploy from the sha- tag or a digest rather than latest, so a rollback is a matter of pointing at the previous one.

Adding a deploy stage

The template stops at building images, because how a project deploys varies. A minimal addition:

deploy:
  stage: deploy
  needs: [docker]
  rules:
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
  environment:
    name: production
    url: https://app.example.com
  script:
    - ./scripts/deploy.sh "$CI_COMMIT_SHORT_SHA"

Run migrations before the new image serves traffic, as a separate job or an init container — not from application startup, where every replica would race.

Environment variables

Set secrets as masked, protected CI/CD variables in project settings. The pipeline itself needs none: SKIP_ENV_VALIDATION=true is set because builds are static and validation happens when the container boots.

Scheduled work

Use a scheduled pipeline calling an authenticated endpoint:

cron:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule" && $CRON_JOB == "digest"'
  script:
    - curl -fsS -H "Authorization: Bearer $CRON_SECRET" "$API_URL/cron/digest"

Runs are logged, retryable and alertable, and the logic stays in the app where it is typed and testable. Avoid in-app schedulers — they fire once per replica.

Make such endpoints idempotent and guard them with a Redis lock: schedulers retry.