.md

CI/CD integration

Run the dcupl CLI inside your pipeline to push workspace changes automatically on every merge. The pattern is the same on every platform: install the CLI, expose your project ID and API key from your CI secrets as environment variables, then push. This page shows the core pattern, what the push changes in the cloud, and ready-to-copy configs for the major platforms.

The pattern

Run the job from the workspace root, the folder that holds dcupl.config.json: the CLI reads that file, and the files under its baseFolder (default dcupl/), from the current directory. In a folder without a dcupl.config.json the push uses the defaults (baseFolder dcupl, include **/*.json, **/*.csv and **/*.js) and says so on stderr.

# DCUPL_PROJECT_ID and DCUPL_API_KEY come from your CI secrets as environment variables
dcupl files push --force --yes

The files commands read DCUPL_PROJECT_ID and DCUPL_API_KEY straight from the environment, so the job needs no dcupl config set step. Use the project's CLI key (type CLI read/write, see API keys): files status, push and pull reject every other key type. --yes keeps the job non-interactive.

What --force overwrites

A CI job starts from a fresh checkout, so it has no sync baseline: the CLI records the last-synced state in .dcupl/sync-state/, which belongs to one checkout and is never committed. Without a baseline, push can't tell your changes from edits made in the Console. Every file whose content differs from the server copy counts as a conflict, and a push without --force aborts with PUSH_CONFLICTS_NO_BASELINE (exit 1) before it uploads anything. --force resolves each conflict by uploading the repository copy.

On a fresh checkout, dcupl files push --force does this to the target version (draft, unless you pass --version <id>):

File What happens
Only in the repository Uploaded
In both, with different content Uploaded: the repository copy replaces the server copy, including Console edits since the last push
In both, with the same content Skipped
Only on the server Kept. Nothing is deleted, with or without --strict

Push never changes your files, and it never touches production: it rejects that version (promote with dcupl files versions copy). Add --path <folder> to limit the push, and what it can overwrite, to part of the workspace.

Watch out

The repository wins. Every run overwrites the target version's files with the repository copy, so Console edits to those files are lost on the next push. Edit that version only in the repository, or pull Console edits into it with dcupl files pull (--force in a checkout that has never synced) and commit them before the next run.

Deletions are not mirrored: --strict deletes a server file only when the baseline shows it was deleted locally, and a CI checkout has no baseline. Remove a file from the cloud version with dcupl files delete --path <path> --yes, or in the Console.

Watch out

Never commit dcupl.secrets.json or .env. Inject the API key from your CI provider's secrets at runtime as the DCUPL_API_KEY environment variable. Make sure .gitignore lists dcupl.secrets.json and .env. dcupl init <folder> writes a .gitignore that does, but dcupl init --here keeps a .gitignore that is already in the folder and adds neither. The CLI keeps .dcupl/ out of Git itself.

If you'd rather have a credentials step, dcupl config set --project-id "$DCUPL_PROJECT_ID" --api-key "$DCUPL_API_KEY" --yes writes the project ID to dcupl.config.json and the key to .env (a fresh checkout has no dcupl.secrets.json). Run it only where dcupl.config.json is committed: without one, it creates a config whose filesUpload.include lacks **/*.js, so scripts, operators and transformers are not pushed.

For a self-hosted Console, commit consoleApiUrl in dcupl.config.json (dcupl config set --console-api-url <url> writes it). There is no environment variable for it: unlike DCUPL_API_KEY and DCUPL_PROJECT_ID, consoleApiUrl is read from the config file only.

Gate the push with validation

Optionally run dcupl validate first and fail the job before any files are uploaded. Remember that validate exits 0 whenever it produces a report, even one with errors, and that the report's top-level ok is always true — check summary.critical and load.ok instead. It exits 1 only when it cannot produce a report (for example, a malformed loader config), so keep the exit-code check too (see Validating a project):

report=$(dcupl validate --json) || { echo "$report"; exit 1; }
loaded=$(echo "$report" | jq -r '.load.ok')
critical=$(echo "$report" | jq -r '.summary.critical')
if [ "$loaded" != "true" ] || [ "$critical" -gt 0 ]; then
  echo "Validation failed — aborting push" && exit 1
fi
dcupl files push --force --yes

Platform examples

.github/workflows/deploy.yml
name: Deploy to dcupl

on:
  push:
    branches: [main]

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

      - uses: actions/setup-node@v7
        with:
          node-version: '24'

      - run: npm install -g @dcupl/cli

      - name: Push to dcupl
        run: dcupl files push --force --yes
        env:
          DCUPL_PROJECT_ID: ${{ secrets.DCUPL_PROJECT_ID }}
          DCUPL_API_KEY: ${{ secrets.DCUPL_API_KEY }}

Store DCUPL_PROJECT_ID and DCUPL_API_KEY as repository secrets under Settings → Secrets and variables → Actions.

.gitlab-ci.yml
deploy:
  image: node:24
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  script:
    - npm install -g @dcupl/cli
    - dcupl files push --force --yes

Define DCUPL_PROJECT_ID and DCUPL_API_KEY as masked CI/CD variables under Settings → CI/CD → Variables. GitLab passes them to the job as environment variables.

.circleci/config.yml
version: 2.1

jobs:
  deploy:
    docker:
      - image: cimg/node:24.21
    steps:
      - checkout
      - run: npm install -g @dcupl/cli
      - run:
          name: Push to dcupl
          command: dcupl files push --force --yes

workflows:
  deploy-on-main:
    jobs:
      - deploy:
          filters:
            branches:
              only: main

Set DCUPL_PROJECT_ID and DCUPL_API_KEY as project environment variables in the CircleCI dashboard. CircleCI passes them to the job as environment variables.