A lightweight, flexible, and powerful pipeline/workflow execution engine written in Go. This tool allows you to define and execute complex build, test, and deployment workflows using simple YAML configuration files.
- Lightweight: Lightweight design with minimal runtime dependencies and fast startup
- YAML-based Configuration: Define your pipelines using intuitive YAML syntax
- Timeout Support: Configure timeouts for individual jobs
- Failure Handling: Control whether job failures should fail the entire pipeline
- Working Directory: Set custom working directories for your pipelines
- Stage Variable Passing: Export job variables from one stage and inject them into later stages
- Extensible: Easy to extend with custom actions and functionality
go install github.com/Meha555/go-pipeline@latestAfter installation, the go-pipeline binary will be available in your $GOPATH/bin or $HOME/go/bin directory (ensure this directory is in your PATH).
- Pipeline: A complete workflow that consists of multiple Stages.
- Stage: A phase within a workflow, representing a complete task that includes multiple Jobs.
- Job: A specific task, which is the indivisible minimum execution unit and contains multiple Actions.
- Action: A specific operation that constitutes a Job and exists outside the workflow concept.
- The workflow starts with a Pipeline. After one Pipeline is executed, the next one will not run automatically and must be specified manually.
- Within a Pipeline, Stages are executed sequentially. If one Stage fails, subsequent Stages will be terminated, and the entire Pipeline will be marked as failed.
- Within a Stage, Jobs are executed in parallel. If any Job fails, the Stage will fail—unless the Job is marked as
allow_failure.
- Only forward dependencies are allowed for all components (to avoid complex dependency relationships).
- Why is Job failure allowed while Stage failure is not? Because a Job, as the minimum execution unit, already contains many Actions and can complete a range of tasks. A Stage only serves to better isolate the relationships and order between Jobs. Therefore, if a Stage might fail, its failure handling must be properly addressed before being written into the configuration file.
You can obtain serval runtime information through builtin environment variables. Use go-pipeline envs to list all builtin envrionment variables.
Go-Pipeline supports sending notifications when a pipeline succeeds or fails. You can configure different types of notifiers in your pipeline configuration file.
notifiers:
email:
server: smtp.example.com
port: 587
from:
username: your-email@example.com
password: your-password
to:
- recipient@example.com
cc:
- cc-recipient@example.com
# Work in Process
# bot:
# server: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your-webhook-key"
# sms:
# server: "https://sms.api.qq.com/send"
# appid: "your-app-id"
# appkey: "your-app-key"Create a file named pipeline.yaml:
name: "cmake-pipeline"
version: "1.0.0"
# Optional. Defaults to cmd on Windows and sh on Unix-like systems.
# Supported values: cmd, sh, bash, powershell.
# shell: "sh"
cron: "1 * * * *"
envs:
CMAKE_GENERATOR: Ninja
MOTTO: "An apple a day $(date +%Y-%m-%d), keeps the `echo 'doctor'` away"
workdir: "D:\\Codes\\C++\\myproject"
stages:
- build
- test
- cleanup
skips:
- test
- cleanup_job
build_job:
stage: build
envs:
CMAKE_BUILD_TYPE: Release
actions:
- echo "$STAGE_NAME - $JOB_NAME"
- cmake -S . -B build -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE
- cmake --build build -j8
hooks:
before:
- echo "Before build"
- "echo \"MOTTO: $MOTTO\""
after:
- echo "After build"
- "echo \"build dir: $(pwd)/build\""
test_job:
stage: test
actions:
- echo "$STAGE_NAME - $JOB_NAME"
- ctest --test-dir build
- | # 多行命令,可用于让cd和设置变量之类在“多条指令(单个action)”中生效
echo "large script"
cwd=`pwd`
pwd
echo "line 1, cd to $HOME"
cd $HOME
pwd
echo "line 2, cd to $cwd"
cd -
pwd
echo "line 3"
timeout: 5m
allow_failure: yes
cleanup_job:
stage: cleanup
actions:
- echo "$STAGE_NAME - $JOB_NAME"
- rm -rf build
allow_failure: yesUse envs to define environment variables in the same shape as GitLab CI variables, with this project's keyword name. Top-level envs are available to every job. Job-level envs are available only to that job and override top-level variables with the same name.
envs:
MODE: global
OUT_DIR: build/out
build_job:
stage: build
envs:
MODE: build
PACKAGE_DIR: "$OUT_DIR/$JOB_NAME"
actions:
- echo "$MODE"
- echo "$PACKAGE_DIR"JOB_NAME is a job-level builtin variable injected into each job's actions and hooks. It is not written to the parent process environment, so jobs running in parallel do not overwrite each other's JOB_NAME.
Use job-level rules to decide whether a job should run. rules must be a non-empty list. Each rule can define on; if on is omitted, that rule defaults to true. A job runs when any rule matches. If no rules match, the job is skipped successfully.
envs:
RUN_BUILD: "true"
build_job:
stage: build
rules:
- on: $RUN_BUILD
- on: python scripts/should_build.py
actions:
- echo buildon supports variable references and shell commands:
$VARor${VAR}reads the variable and applies truthy matching. Empty,0,false,no, andoffare false; other values are true.- Any other string is executed with the pipeline shell in the pipeline
workdir. Exit code0is true; non-zero is false. trueandfalseYAML booleans are supported directly.
Rules are supported on jobs only. Stages do not have rules; if every job in a stage is skipped, the stage completes successfully.
Jobs can export variables with the exports keyword. After all jobs in a stage finish successfully, Go-Pipeline injects each exported entry into the pipeline environment. Later stages can use these variables in actions and hooks.
stages:
- build
- test
build_job:
stage: build
actions:
- make build
exports:
BUILD_DIR: build/release
BUILD_VERSION: 1.2.3
test_job:
stage: test
actions:
- echo "Build dir: $BUILD_DIR"
- echo "Build version: $BUILD_VERSION"exports uses the same map shape as envs. Export values support variable references and inline shell commands using the same resolution rules as envs.
Variables are only guaranteed to be available to later stages. Jobs in the same stage run in parallel, so they should not depend on each other's exports.
Pipeline files can include other local YAML files before validation and execution. This is useful for sharing common stages, jobs, environment variables, and notifier configuration.
includes: base.yamlincludes:
- base.yaml
- jobs/*.yaml
- jobs/**/*.ymlYou can declare includes more than once in the same YAML file. Blocks are processed in the order they appear:
includes:
- base.yaml
includes:
- jobs/*.yamlInclude paths are resolved relative to the YAML file that declares includes. For example, if configs/main.yaml includes base.yaml, Go-Pipeline loads configs/base.yaml. Nested includes are resolved relative to the nested file.
Wildcard includes support * and **. Matched files are loaded in file-name order for stable merge behavior. If two matches have the same file name, the full path is used as a tie-breaker. A wildcard that matches no files is treated as an error.
Included files are merged first, then the current file is merged on top. This matches GitLab-style precedence: local values override included values. Top-level jobs with the same name are merged by field, so a local job can override actions while keeping an included stage or timeout. Sequence fields such as stages, skips, actions, hooks.before, and hooks.after are replaced as a whole, not appended. Top-level envs are replaced as a whole. Job-level envs are merged by key because they are part of the job mapping.
The singleton fields name, version, shell, cron, workdir, and stages can appear only once across the full include chain. If any included or current file defines one of these fields more than once, parsing fails instead of overriding it.
When a later file overrides an existing key, Go-Pipeline prints a warning to stderr, for example:
warning: key "build_job.actions" from configs/main.yaml overrides value from configs/base.yaml
Each pipeline runs actions through a shell. If shell is omitted, Go-Pipeline selects the platform default shell:
- Windows:
cmd /c - Linux/macOS and other non-Windows platforms:
sh -c
You can override it in the YAML configuration:
name: "example"
version: "1.0.0"
shell: "sh" # cmd, sh, bash, or powershellThe selected shell is also exposed as the builtin PIPELINE_SHELL environment variable.
Action command handling depends on the selected shell:
cmd: Go-Pipeline applies a small safe command-line splitter before invokingcmd /c. This supports single-quoted raw strings for paths that contain spaces, becausecmddoes not treat single quotes as quotes.shandbash: actions are passed as raw shell lines. Use normal POSIX shell syntax.powershell: actions are passed as raw PowerShell commands. Use PowerShell's call operator (&) when executing a quoted path.
Examples for an executable path with spaces:
# Windows default cmd: Go-Pipeline strips the single quotes and preserves the path as one argument.
actions:
- "'C:\\Program Files\\LLVM\\bin\\clang++.exe' --version"
# sh/bash: the shell understands single-quoted paths directly.
shell: "sh"
actions:
- "'C:\\Program Files\\LLVM\\bin\\clang++.exe' --version"
# powershell: use the call operator for quoted command paths.
shell: "powershell"
actions:
- "& 'C:\\Program Files\\LLVM\\bin\\clang++.exe' --version"For cmd, the safe splitter is intentionally narrow: it is meant to preserve single-quoted command/path segments with spaces. For complex shell syntax, choose a shell that natively supports the syntax you need, such as sh, bash, or powershell.
Go-Pipeline writes logs to stderr so stdout remains available for command output.
By default, logs use human-readable console format at info level. Console logs are intentionally compact: they show only timestamp, level, and message. Structured fields are hidden in console mode so command output stays easy to read:
./go-pipeline run -f pipeline.yamlExample console output:
2026-06-17T17:51:02+08:00 INF Stage@build: 1 jobs
2026-06-17T17:51:02+08:00 INF Job@build_job success
2026-06-17T17:51:02+08:00 INF Success (3 succeed/3 total)
Use JSON format when you need structured fields for log processing. JSON logs keep all fields, including pipeline context inherited through the logger hierarchy:
./go-pipeline --log-format json run -f pipeline.yamlExample JSON output:
{"level":"info","pipeline":"include-demo","version":"1.0.0","stage":"build","job":"build_job","actions":2,"time":"2026-06-17T17:51:02+08:00","message":"Job@build_job: 2 actions"}The logger hierarchy is:
- Pipeline logs carry
pipelineandversion. - Stage logs inherit Pipeline fields and add
stage. - Job logs inherit Stage fields and add
job. - Action logs use the global logger and do not inherit pipeline, stage, or job context.
Internally, Go-Pipeline uses the standard library log/slog API and routes logs through zerolog for encoding and output.
Console color defaults to auto, which enables color only when stderr is a terminal that supports it.
You can switch format, level, and color behavior with CLI flags:
./go-pipeline --log-format json --log-level debug --log-color never run -f pipeline.yamlSupported formats:
consolejson
Supported levels:
debuginfowarnerrordisabled
Supported color modes:
autonever
auto enables color only when stderr is a terminal that supports it. never disables color.
Environment variables can also set defaults:
PIPELINE_LOG_FORMAT=json PIPELINE_LOG_LEVEL=warn PIPELINE_LOG_COLOR=never ./go-pipeline run -f pipeline.yamlCLI flags take precedence over environment variables. Every log event includes an RFC3339 timestamp; PIPELINE_LOG_TIMESTAMP is not supported.
./go-pipeline run -f pipeline.yaml