WooForge docs
Everything you need to get your code onto WooForge and write a pipeline that builds, tests, and deploys it — including infrastructure changes via Terraform.
Overview
A WooForge project bundles a Git repository, merge requests, issues, and a pipeline runner under one URL. There is no separate app to configure — everything a project needs to build and ship lives in the repository itself, under a single directory: .wooforge/.
Getting started
Every project can be reached over HTTP or SSH, the same as any other Git remote.
Clone over HTTPS
Use a Personal Access Token as your password — generate one under Settings → Access Tokens.
git clone https://<your-host>/group/project.gitClone over SSH
Add a public key under Settings → SSH Keys, then:
git clone ssh://git@<your-host>:2222/group/project.gitTrigger a pipeline
Pipelines start automatically on every push to a branch that has a .wooforge/ directory, or on demand from the Pipelines tab using “Run pipeline” against any branch.
The .wooforge/ structure
Pipeline configuration is split across three files instead of one, so a long pipeline stays readable: each stage’s jobs live in their own file, and two small entry files say what runs and in what order.
.wooforge/
├── cicd.yml # entry point: metadata, triggers, global variables
├── stage.yml # ordered stage list + which file implements each
└── stages/
├── build.yml # jobs for the build stage
├── test.yml # jobs for the test stage
└── deploy.yml # jobs for the deploy stage (terraform lives here too)cicd.yml — entry point
version: 1
pipeline:
name: My Project CI/CD
triggers: [push, merge_request, manual]
variables:
NODE_ENV: production
stages_file: .wooforge/stage.ymlvariables here are global — every job gets them, unless a stage or job sets the same key again, which wins.
stage.yml — stage order
stages:
- name: build
file: .wooforge/stages/build.yml
- name: test
file: .wooforge/stages/test.yml
depends_on: [build]
- name: deploy
file: .wooforge/stages/deploy.yml
depends_on: [test]Stage order is resolved from depends_on, not just list order. A cycle here fails the pipeline immediately with the cycle spelled out, rather than hanging.
stages/*.yml — jobs
jobs:
unit_test:
image: node:20-alpine
needs: [build]
commands:
- npm run test:unit
lint:
image: node:20-alpine
needs: [build]
commands:
- npm run lint
allow_failure: trueneedscan point at a job in an earlier stage or another job in the same file — that’s how a Terraform apply waits for its own plan job even though both live in the deploy stage.
Job field reference
Every job under a stage file’s jobs: map accepts:
shell (default) or terraform.alpine:latest.steps reads better for Terraform CLI calls.on_success (default) or manual — a manual job waits for approval in the Pipeline tab.if: conditions. If none match, the job is left out of the pipeline entirely.needs) can pick them up.Terraform & approval gates
Provisioning infrastructure is a job type, not a bolt-on: set type: terraform and the job gets working_directory, backend_config, and automatic TF_VAR_<name> injection for any CI/CD variable flagged for it.
terraform apply is forced onto when: manual— even if you don’t set it, and you cannot turn it off.jobs:
provision_infra:
type: terraform
image: hashicorp/terraform:1.8
working_directory: infra/
backend_config: infra/backend.hcl
variables:
TF_VAR_environment: production
steps:
- terraform init -backend-config=backend.hcl
- terraform validate
- terraform plan -out=tfplan
artifacts:
paths: [infra/tfplan]
rules:
- if: '$TARGET_BRANCH == "main"'
apply_infra:
type: terraform
image: hashicorp/terraform:1.8
working_directory: infra/
needs: [provision_infra]
steps:
- terraform apply -auto-approve tfplan
environment:
name: productionPlan and apply are deliberately separate jobs: the plan runs unattended and uploads tfplan as an artifact; apply pulls that exact plan back down and runs it only once someone clicks Approve & run in the Pipeline view, where it shows an Infrabadge and an “Awaiting approval” state until then.
Rules & variable interpolation
rules conditions and variables values can reference other variables with $NAME or ${NAME}. Only == / != string comparisons are supported in if:, which covers the common case: branch-gating a job.
rules:
- if: '$TARGET_BRANCH == "main"'An unresolved reference is left as literal text, so it can still be picked up by the shell at run time — useful for CI/CD variables that are secret and shouldn’t be baked into stored config.
CI/CD variables
Set project-level variables under Settings → CI/CD Variables. Every variable becomes a plain environment variable in every job; three flags change how far it reaches:
TF_VAR_<name>.Predefined variables
Available in every job without declaring anything:
| Variable | Value |
|---|---|
| TARGET_BRANCH | The branch the pipeline is running against |
| PIPELINE_SOURCE | push or manual |
| WOO_COMMIT_SHA / _SHORT_SHA | Full and 8-character commit SHA |
| WOO_COMMIT_REF_NAME | Same as TARGET_BRANCH |
| WOO_PROJECT_ID / _PATH / _NAME | Identify the project the pipeline belongs to |
| WOO_PIPELINE_ID / _IID | Internal and project-scoped pipeline numbers |
| WOO_JOB_ID / _NAME / _STAGE | Identify the running job itself |
| WOO_SERVER_URL | Base URL of this WooForge instance |
| CI / WOOFORGE_CI | "true" — useful for scripts that behave differently in CI |