@mockholm/ci-apm-trace
v1.1.19
Published
Sends CI pipeline traces to Elastic APM
Readme
ci-apm-trace
Sends CI pipeline traces to Elastic APM. The same core lifecycle logic powers every distribution channel:
- npm package —
@mockholm/ci-apm-trace. Install it globally and run theci-apm-traceCLI anywhere (any CI or locally). - Docker image —
mockholm/ci-apm-trace(Docker Hub, also on GHCR). No build required. - GitHub Action — a native
uses:step (see GitHub Action). - Azure DevOps Task — an extension task with
PreJob/main/PostJobhandlers (see Azure DevOps). - Kubernetes — run the client as a k8s Job or Pod (see Kubernetes).
- go-task — a
Taskfilewraps the CLI for local runs and CI orchestration (see Taskfile). - CLI profiles — pick which CI platform's metadata to capture with
--ci_platform(see Running the client).
Every mode records a pipeline transaction with step spans and CI metadata labels, then ends the trace as success or failure. The CLI splits each run into pre / main / post subcommands (see CLI usage); the GitHub Action maps those to its main / post phases and the Azure DevOps task to its PreJob / main / PostJob handlers.
How it works
- The CLI initializes the APM client from environment variables.
- A pipeline transaction is started with the name
--trace-name(the build ID is appended when provided, e.g.release-42). - Each
addStep()records a span under the pipeline transaction. - The trace ends as
successorfailure(with the error captured) and is flushed to APM Server.
The following labels are set on every trace when the corresponding value is available:
| Label | CLI option | Source |
| --- | --- | --- |
| build_id | --build-id | CLI / CI variable |
| build_number | --build-number | CLI / CI variable |
| branch | --branch | CLI / CI variable |
| commit | --commit | CLI / CI variable |
| repo | --repo | CLI / CI variable |
| ci_provider | --ci-provider | CLI / CI variable |
| runner_os | — | RUNNER_OS env var |
| runner_arch | — | RUNNER_ARCH env var |
ECS system metrics
Every post run also collects an ECS 8.11 system metrics record of the host
at pipeline end using systeminformation, and sends it as metricset.name: system
APM metricsets (CPU, memory, and one per filesystem mount). The ECS record
carries the mandatory event.kind/event.category/event.type/ecs.version
fields and the APM correlation fields (service.name, service.version,
agent.name, host.name, OS name/version), with the sample fields
system.cpu.*, system.memory.*, and system.filesystem.*. The metrics land
in the metrics-apm.app.* data stream and can be queried like standard
system metrics (e.g. system.cpu.total.pct, system.memory.used.pct).
Node.js runtime metrics
Every post run also sends a metricset.name: nodejs runtime metricset from the
same process, mirroring the fields the official elastic-apm-node agent reports:
nodejs.memory.heap.used.bytes, nodejs.memory.heap.allocated.bytes,
nodejs.memory.external.bytes, nodejs.memory.arrayBuffers.bytes,
nodejs.handles.active, nodejs.requests.active, and
nodejs.eventloop.delay.avg.ms. The client reports agent.name: nodejs in the
APM metadata (overridable via the agentName init option), so Kibana's
APM → Metrics tab resolves the Node.js runtime dashboard and the
system.cpu.total.norm.pct / system.process.cpu.total.norm.pct fields on the
system metricset populate its CPU charts.
Requirements
- Node.js 18+ (20 recommended) — only needed for the npm/CLI and for building from source. The Docker image and Kubernetes Job bundle Node.js.
- An Elastic APM Server endpoint.
Installation
Install the CLI globally from npm — no source checkout needed:
npm install -g @mockholm/ci-apm-traceThe ci-apm-trace binary is now on your PATH:
ci-apm-trace --versionTo build from source instead (e.g. to develop or patch the client):
npm install
npm run buildThe compiled output lands in dist/; node dist/cli.js is the same CLI as the installed ci-apm-trace binary.
Configuration
The CLI reads the following environment variables. All are optional — without
ELASTIC_APM_SERVER_URL the client stays inactive and nothing is sent.
APM Server connection
| Variable | Description | Default |
| --- | --- | --- |
| ELASTIC_APM_SERVER_URL | APM Server URL, e.g. https://apm.example.com | unset (client inactive) |
| ELASTIC_APM_SECRET_TOKEN | APM Server secret token | unset |
| ELASTIC_APM_API_KEY | APM Server API key (overrides the secret token) | unset |
| ELASTIC_APM_SERVICE_NAME | Service name shown in APM | selected profile name (e.g. npm) |
| ELASTIC_APM_DEBUG | Print the APM server response body (true/1) | false |
The service environment is always set to ci. The default service name is the selected profile's name (npm, github-action, azure-devops, team-city, jenkins, docker, k8s, or task) unless ELASTIC_APM_SERVICE_NAME is set; the service version defaults to the build number (BUILD_NUMBER / Build.BuildNumber) and the service node to AGENT_NAME / RUNNER_NAME (or the hostname).
Trace state (handed between pre, main, and post)
| Variable | Set by | Read by | Purpose |
| --- | --- | --- | --- |
| APM_TRACE_ID | pre | main, post | Trace ID |
| APM_TRANSACTION_ID | pre | main, post | Pipeline transaction ID |
| APM_SPAN_ID | pre | post | Job End span ID |
| APM_JOB_START_MS | pre | post | Job start time (epoch ms); post derives the duration from it |
Optional CI metadata
The following variables enrich the trace with labels (build_id, branch,
commit, ...), ci.*/vcs.* tags, the APM user, and custom fields. The Azure
task sets the Build.*/Agent.* equivalents automatically; the CLI and GitHub
wrapper read the GITHUB_* / RUNNER_* / BUILD_* variants instead.
| Variable | Used for | Notes / fallback |
| --- | --- | --- |
| CI_PROVIDER | ci_provider label and span subtype | selected profile name (see Running the client) |
| BUILD_DEFINITION_NAME, CI_PIPELINE_NAME | definition_name, ci.pipeline.name, log messages | ci-pipeline |
| BUILD_ID | build_id, ci.pipeline.id, ci.pipeline.run.id | — |
| BUILD_NUMBER | build_number, ci.pipeline.run.number, service version | — |
| BUILD_BRANCH | branch, ci.build.ref | — |
| BUILD_COMMIT | commit, ci.build.commit, vcs.commit.id | — |
| BUILD_REPO | repo, ci.build.repo | — |
| BUILD_REPO_URI | vcs.repository.url, custom repo_uri | — |
| BUILD_REF | vcs.ref.head.name | falls back to BUILD_BRANCH |
| BUILD_URL | ci.pipeline.run.url, custom build_url | — |
| BUILD_DEFINITION_ID | custom definition_id | — |
| JOB_STATUS | ci.job.status, ci.pipeline.run.result | post treats Failed/Canceled as a failure; else Succeeded |
| JOB_ID | ci.job.id, custom job_id | — |
| JOB_NAME | ci.job.name, custom job_name | — |
| RUNNER_OS | runner_os | — |
| RUNNER_ARCH | runner_arch | — |
| AGENT_NAME, RUNNER_NAME | ci.pipeline.agent.name, custom agent_name, service node | hostname |
| AGENT_VERSION | custom agent_version | — |
| GITHUB_ACTOR, BUILD_REQUESTED_FOR | ci.pipeline.run.user, user username, custom requested_for | — |
| GITHUB_ACTOR_ID, BUILD_REQUESTED_FOR_ID | user id | — |
| GITHUB_ACTOR_EMAIL, BUILD_REQUESTED_FOR_EMAIL | user email | — |
.env file support
The CLI auto-loads a .env file from the current working directory at startup,
so connection and CI metadata variables can live in a checked-in-free file
instead of the shell environment. The file uses the same variables as
Configuration and Optional CI metadata:
# .env
# --- APM Server connection ---
ELASTIC_APM_SERVER_URL=https://apm.example.com
ELASTIC_APM_SECRET_TOKEN=my-token
ELASTIC_APM_SERVICE_NAME=my-pipeline
# --- pipeline context ---
BUILD_ID=42
BUILD_NUMBER=7
BUILD_BRANCH=main
BUILD_COMMIT=abc123
BUILD_REPO=acme/my-repo
CI_PROVIDER=npmci-apm-trace --trace-name release --build-id 42With the file above, this is equivalent to:
ELASTIC_APM_SERVER_URL=https://apm.example.com \
ELASTIC_APM_SECRET_TOKEN=my-token \
ELASTIC_APM_SERVICE_NAME=my-pipeline \
BUILD_ID=42 BUILD_NUMBER=7 BUILD_BRANCH=main BUILD_COMMIT=abc123 \
BUILD_REPO=acme/my-repo CI_PROVIDER=npm \
ci-apm-trace --trace-name releaseRules:
- Loading is opt-in per file: nothing happens if
.envdoes not exist. - Values are parsed as simple
KEY=VALUElines —#comments, blank lines, an optionalexportprefix, and single/double quotes are supported. - Existing environment variables always win — a
.envvalue never overrides a variable already set in the environment. - Use
--env-file <path>to load a different file (relative paths are resolved from the current directory), or--env-file=to disable loading entirely. The option works with the one-shot mode and thepre/main/postsubcommands.
ci-apm-trace --env-file ./config/custom.env --trace-name release --build-id 42
ci-apm-trace pre --env-file .env.local --trace-name releaseCI security: never commit secrets. Keep
.envin.gitignore(this repo does), and instead configureELASTIC_APM_SECRET_TOKENas a CI secret or repository variable. The file support is a local-development convenience.
CLI usage
The CLI is a yargs command client with two modes: a one-shot mode for
tracing a single run, and a pre/main/post mode for splitting a trace
across several pipeline steps (the CLI equivalent of the Azure task's
PreJob/main/PostJob handlers).
ci-apm-trace [command]
Commands:
ci-apm-trace pre Start the trace: generate IDs and emit APM_* environment
variables for main/post
ci-apm-trace main Record the main task execution span under the running trace
ci-apm-trace post End the trace: transaction, error, and metrics for the
completed pipeline
Options:
--version Show version number [boolean]
--trace-name Name of the pipeline trace [string] [default: "ci-pipeline"]
--build-id CI build/pipeline ID [string]
--build-number CI build number [string]
--branch Git branch [string]
--commit Git commit SHA [string]
--repo Repository name [string]
--ci-provider CI provider name [string]
--ci_platform CI platform profile to use (npm, github-action, azure-devops,
team-city, jenkins, docker, k8s, task) [string]
--env-file Path to an .env file to load (default: .env in the current
directory) [string]
--fail Simulate a pipeline failure [boolean] [default: false]
--debug Show the APM server response in the output
[boolean] [default: false]
--help Show help [boolean]--ci_platform selects which profile captures the CI metadata (see
Running the client). The default profile is npm. It can
also be set with the APM_CI_PLATFORM environment variable.
One-shot mode
Runs the whole trace (start → step → end) in a single process. Use this when you only need to trace one pipeline step or a job's main command.
Success (exit code 0):
ELASTIC_APM_SERVER_URL=https://apm.example.com \
ELASTIC_APM_SECRET_TOKEN=my-token \
ci-apm-trace \
--trace-name release \
--build-id 42 \
--build-number 7 \
--branch main \
--commit abc123 \
--repo acme/my-repo \
--ci-provider cliFailure (exit code 1, error captured in APM):
ELASTIC_APM_SERVER_URL=https://apm.example.com \
ci-apm-trace --trace-name release --build-id 42 --failRun without installing globally:
node dist/cli.js --trace-name my-pipeline --build-id 1pre/main/post mode
For multi-step pipelines, split the trace across commands. pre generates the
trace IDs and prints export APM_* lines for the shell to source; main and
post read them back from the environment:
# Step 1: start the trace (capture and persist the IDs)
eval "$(ci-apm-trace pre --trace-name release)"
# Step 2: after the pipeline work, record the "Main Task Execution" span
ci-apm-trace main --trace-name release
# Step 3: end the trace (transaction, metrics, and error on failure)
ci-apm-trace post --trace-name release
ci-apm-trace post --trace-name release --fail # exits 1In CI, persist the four APM_* variables produced by pre across the steps
(GitHub Actions $GITHUB_ENV, Azure pipeline variables, etc.) instead of
eval. The pre/main/post subcommands accept --debug, and post
accepts --fail:
| Command | Options |
| --- | --- |
| pre | --trace-name (default ci-pipeline), --debug |
| main | --trace-name (default ci-pipeline), --debug |
| post | --trace-name (default ci-pipeline), --fail, --debug |
post also honors JOB_STATUS=Failed|Canceled to end the trace as a failure
and BUILD_NUMBER to append to the transaction name. See
Trace state for the variables
passed between commands and Configuration for the connection
and CI metadata variables.
Running the client
The client is a single CLI at its core, so it runs anywhere Node.js, a container
runtime, or a Kubernetes cluster is available. --ci_platform (or the
APM_CI_PLATFORM environment variable) selects the profile that controls the
default service name and which CI environment variables are captured as labels,
tags, and custom fields:
| Profile | Default service.name | Reads from | Typical platforms |
| --- | --- | --- | --- |
| npm | npm | npm_*, npm_config_*, and CI_* variables | npm scripts, GitLab CI, CircleCI, any npm-capable runner |
| github-action | github-action | GITHUB_*, RUNNER_* | GitHub Actions |
| azure-devops | azure-devops | Build.*, Agent.*, SYSTEM_* | Azure Pipelines |
| team-city | team-city | TEAMCITY_*, BUILD_* | TeamCity |
| jenkins | jenkins | BUILD_*, GIT_*, JOB_* | Jenkins |
| docker | docker | container ID / hostname | Docker containers |
| k8s | k8s | pod metadata (downward API) | Kubernetes |
| task | task | CI_*, BUILD_* (or CI_PROVIDER) | go-task driven pipelines |
Set the profile explicitly when running on a specific platform:
# GitHub Actions runner
ci-apm-trace pre --ci_platform github-action
# Azure Pipelines agent
ci-apm-trace pre --ci_platform azure-devops
# TeamCity / Jenkins
ci-apm-trace pre --ci_platform team-city
ci-apm-trace pre --ci_platform jenkins
# go-task
task pre PROFILE=taskEach platform below links to a dedicated section: npm/CLI, GitHub Action, Azure DevOps, Docker, Kubernetes, Taskfile, and the ready-made client pipelines.
GitHub Action
The action is a JavaScript action with main and post sections — the GitHub
equivalent of the Azure task's PreJob/main/PostJob handlers — and runs the
same CLI with the github-action profile (service.name: github-action):
- Main phase (
dist/github-wrapper.js) runscli preto start the trace and persist the trace IDs, thencli mainto record theMain Task Executionspan. - Post phase (
dist/github-post.js) runs automatically when the job finishes — even if later steps failed — and runscli postto close theJob Endspan, the pipeline transaction, and the metrics. The trace ends as a failure whenever any step in the job failed or the job was cancelled.
action.yml defines the inputs trace-name (default github-action), fail
(default false), debug (default false), apm-server, and apm-token,
plus a hidden __job-status input (default ${{ job.status }}) that tells the
post phase the job's final status. The wrapper reads GITHUB_RUN_ID,
GITHUB_RUN_NUMBER, GITHUB_REF_NAME, GITHUB_SHA, GITHUB_REPOSITORY,
GITHUB_WORKFLOW, GITHUB_JOB, GITHUB_ACTOR, RUNNER_OS, and RUNNER_ARCH
automatically. The apm-server and apm-token inputs override the
ELASTIC_APM_SERVER_URL / ELASTIC_APM_SECRET_TOKEN environment variables.
Prefer this action for tracing a whole GitHub Actions job. To trace a pipeline without the wrapper (e.g. from
npmscripts or the Taskfile), use the CLI'sgithub-actionprofile or the Client via npm workflow.
Example workflow (place the action step anywhere in the job — the trace is ended after all steps run, so it reflects the job's final status):
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: npm ci && npm run build
- name: Send pipeline trace to APM
uses: mockholm/ci-apm-trace@v1
with:
trace-name: ci-build
apm-server: ${{ secrets.ELASTIC_APM_SERVER_URL }}
apm-token: ${{ secrets.ELASTIC_APM_SECRET_TOKEN }}Force a failure trace for testing:
- name: Simulate failure trace
uses: mockholm/ci-apm-trace@v1
with:
trace-name: smoke-test
fail: 'true'With fail: 'true' the main phase exits with code 1 (failing the step) and the
post phase ends the trace as failed. Any other failing or cancelled step in the
job ends the trace as failed too.
Publishing
The project ships four distribution channels, all released from the
.github/workflows/publish.yml pipeline (one manual dispatch per release):
| Channel | Artifact | Release mechanism |
| --- | --- | --- |
| npm | @mockholm/ci-apm-trace | npm publish --access public (requires the NPM_TOKEN secret) |
| Docker | mockholm/ci-apm-trace on Docker Hub + ghcr.io/<owner>/ci-apm-trace | multi-arch build & push (requires DOCKERHUB_USERNAME/DOCKERHUB_TOKEN secrets) |
| GitHub Action | Marketplace listing from the vX.Y.Z release | semver tags + GitHub release |
| Azure DevOps | .vsix extension | packaged, uploaded as an artifact, and optionally published with publish_azure: true (requires the AZURE_DEVOPS_EXTENSION_PAT secret) |
The workflow bumps package.json (and syncs vss-extension.json/task.json),
creates the vX.Y.Z, vX.Y, and vX tags, and drives each channel job. See
the platform-specific sections below for details.
Publishing the GitHub Action to the Marketplace
GitHub Actions are published to the GitHub Marketplace by creating a release from a semver tag on the repository's default branch. There is no separate upload — the Marketplace picks up the action.yml at the repo root automatically.
How the action is packaged
The action is a JavaScript action that runs dist/github-wrapper.js (main phase) and dist/github-post.js (post phase), both of which spawn dist/cli.js. Both dist/ and node_modules/ are normally gitignored, so a plain tag/release would check out a broken action. To make it self-contained, npm run bundle:github compiles dist/cli.js into a single @vercel/ncc bundle (plus its modules/ assets) that needs no runtime node_modules; the two wrapper scripts use only Node.js built-ins. The release commit must include that bundled dist/.
Prerequisites
- A public GitHub repository with
action.ymlat the root (already has the requiredname,description, andbranding) - Write access to create tags and releases
Publish via the workflow (recommended)
- Go to Actions > Publish.
- Click Run workflow and enter the version (
patch,minor,major, or e.g.1.0.0). - The workflow bumps
package.json, builds and bundles the action, commitsdist/, creates thevX.Y.Z,vX.Y, andvXtags, pushes, and creates a GitHub release — which publishes the action to the Marketplace. The rollingv1/v1.xtags keep existinguses: ...@v1references working.
Publish manually
npm install
npm run build
npm run bundle:github
npm version 1.0.1 --no-git-tag-version
git add -f dist package.json package-lock.json
git commit -m "chore(release): v1.0.1"
git tag v1.0.1
git tag v1.0
git tag v1
git push origin HEAD v1.0.1 v1.0 v1
gh release create v1.0.1 --generate-notesOr via Task:
task publish:github VERSION=1.0.1Notes
- The action must be released from the repository's default branch to appear in the Marketplace.
- Releases created as a draft or pre-release are not published to the Marketplace.
- Bump the version for each release; the rolling
v1tag is what existing pipelines reference.
Azure DevOps
The Azure DevOps task is an extension (vss-extension.json) that ships three pieces:
CiApmTrace@1task — a service-connection-backed task withPreJob, main, andPostJobhandlersapmservice connection type — a "New service connection" entry named Elastic APM that stores the APM Server URL and secret token
The task runs the same client core as the CLI's azure-devops profile
(service.name: azure-devops). If you prefer not to install the extension, the
ci-apm-trace CLI can be used directly in Azure Pipelines with
--ci_platform azure-devops (see the client pipelines for
ready-made examples), or use the task extension steps.
What the task sends
- PreJob handler (
dist/azure-prejob.js) opens aJob Startspan, generates a trace ID, and persistsAPM_TRACE_ID,APM_SPAN_ID, andAPM_JOB_START_MSfor the rest of the job. - Main handler (
dist/azure-main.js) records aMain Task Executionspan under the same trace. - PostJob handler (
dist/azure-postjob.js) closes theJob Endspan, sends the wrapping pipeline transaction (named fromtraceName, with the build ID appended), and records an error when the job failed and metrics (ci.job.duration.ms,ci.job.success) plus the ECS system metrics on every run.
All events are POSTed to {server}/intake/v2/events as NDJSON with Authorization: Bearer <token>.
The task reads Build.BuildId, Build.BuildNumber, Build.SourceBranchName, Build.SourceVersion, Build.Repository.Name, Agent.OS, and Agent.OSArchitecture and attaches them as tags (build_id, build_number, branch, commit, repo, ci_provider, runner_os, runner_arch).
1. Create the service connection
Once the extension is installed, in your project go to Project settings > Pipelines > Service connections > New service connection and pick Elastic APM:
- APM Server URL — e.g.
https://apm.example.com:8200 - APM Secret Token — the token configured on the APM Server (leave blank if unauthenticated)
2. Add the task to your pipeline
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- script: npm ci && npm run build
- task: CiApmTrace@1
displayName: 'Send pipeline trace to APM'
inputs:
apmConnection: 'Elastic APM'
traceName: 'azure-pipeline'
fail: false
debug: falseInputs:
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| apmConnection | connectedService:apm | — | The Elastic APM service connection (required) |
| traceName | string | azure-devops | Name of the pipeline trace |
| fail | boolean | false | Force the trace to end as a failure (for testing) |
| debug | boolean | false | Show the APM server response body in the build log |
Migrating from v1.0.x: the
apmServerandapmTokeninputs were replaced by theapmConnectionservice connection. Update existing pipelines to select the connection instead of passing those inputs.
Publishing to the Azure DevOps Marketplace
The task is distributed as an Azure DevOps extension (a .vsix file). The repository already contains everything needed to package it:
vss-extension.json # extension manifest (edit your publisher id)
CiApmTrace/ # task folder, generated at package time (gitignored)
icons/ # Marketplace icons (generated via `npm run icons`)
overview.md # Marketplace details page
scripts/package-azure.js # packaging script
.github/workflows/publish.yml # unified release pipeline (also builds this extension)Prerequisites
- Node.js 18+ and
npm installcompleted - A publisher ID registered at https://marketplace.visualstudio.com/manage
- A Personal Access Token (PAT) from
https://dev.azure.com/<org>/_usersSettings/tokenswith the Marketplace > Manage scope
1. Configure the publisher
Open vss-extension.json and replace your-publisher-id with your registered publisher ID:
{
"publisher": "mycompany-myteam",
...
}Update the author in task.json and the repo link in overview.md to match.
2. Build and package
npm install
npm run build
npm run icons
npm run package:azureThe task folder is assembled with its production dependencies and a .vsix is written to out/:
out/mycompany-myteam.ci-apm-trace-1.0.0.vsixThe package script prunes test fixtures from node_modules (they contain paths with spaces that are invalid in a .vsix) and uses tfx to build the archive.
3. Publish to the Marketplace
npx tfx extension publish \
--manifest-globs vss-extension.json \
--token "$AZURE_DEVOPS_EXTENSION_PAT" \
--output-path out \
--no-wait-validationTo publish without waiting for the Marketplace's validation result (useful in CI), pass --no-wait-validation — the workflow already does this.
4. Install the extension
- Go to Organization settings > Extensions in Azure DevOps.
- Select Browse marketplace.
- Find CI APM Trace, select Get it free, and install it into your organization.
- The
CiApmTrace@1task now appears in the Utility category of the pipeline task picker.
5. Versioning
For every release you must bump both versions:
versioninvss-extension.json(extension version — must be higher than the previously published one)versionintask.json(task version — controls which task version pipelines reference)
You can auto-increment the extension patch version when packaging:
npx tfx extension create --manifest-globs vss-extension.json --rev-version6. Automate with GitHub Actions
The .github/workflows/publish.yml pipeline handles every release channel in one run: it bumps the version (and syncs vss-extension.json/task.json), bundles and releases the GitHub Action, publishes to npm, pushes the Docker image, and packages the Azure DevOps extension. To also publish the extension to the Marketplace, dispatch it with publish_azure set to true and set the AZURE_DEVOPS_EXTENSION_PAT secret to your PAT.
Docker
The published image mockholm/ci-apm-trace (also available as ghcr.io/mockholm/ci-apm-trace) is multi-arch (linux/amd64, linux/arm64) and needs no build step — pull it and run. It embeds Node.js 20, so no local Node.js install is needed.
One-shot trace:
docker run --rm \
-e ELASTIC_APM_SERVER_URL=http://apm:8200 \
-e ELASTIC_APM_SECRET_TOKEN=xyz \
mockholm/ci-apm-trace \
--trace-name docker \
--build-id 123 \
--branch main \
--commit abc123 \
--repo my-repo \
--ci_platform dockerForce a failure trace:
docker run --rm \
-e ELASTIC_APM_SERVER_URL=http://apm:8200 \
-e ELASTIC_APM_SECRET_TOKEN=xyz \
mockholm/ci-apm-trace --trace-name docker --build-id 123 --failSplit a trace across several containers with the pre/main/post
subcommands, persisting the APM_* variables between runs:
# pre: print the APM_* hand-off variables
docker run --rm -e ELASTIC_APM_SERVER_URL=http://apm:8200 -e ELASTIC_APM_SECRET_TOKEN=xyz \
mockholm/ci-apm-trace pre --trace-name docker --ci_platform docker
# main / post: pass APM_TRACE_ID, APM_TRANSACTION_ID, APM_SPAN_ID, APM_JOB_START_MS
docker run --rm -e ELASTIC_APM_SERVER_URL=http://apm:8200 -e ELASTIC_APM_SECRET_TOKEN=xyz \
-e APM_TRACE_ID=... -e APM_TRANSACTION_ID=... -e APM_SPAN_ID=... -e APM_JOB_START_MS=... \
mockholm/ci-apm-trace main --trace-name docker --ci_platform docker
docker run --rm -e ELASTIC_APM_SERVER_URL=http://apm:8200 -e ELASTIC_APM_SECRET_TOKEN=xyz \
-e APM_TRACE_ID=... -e APM_TRANSACTION_ID=... -e APM_SPAN_ID=... -e APM_JOB_START_MS=... \
mockholm/ci-apm-trace post --trace-name docker --ci_platform dockerThe container exits with the CLI's exit code (0 on success, 1 on failure).
To build and tag the image locally instead of pulling it:
docker build -t mockholm/ci-apm-trace .Kubernetes
Run the client as a Kubernetes Job with the k8s profile. The pod runs the
full pre -> main -> post lifecycle in one shell, so the trace state is
handed off natively.
Create the secret with your APM endpoint (see
k8s.ymlfor the exact key names):kubectl create secret generic apm-secret \ --from-literal=server-url=https://<deployment-id>.apm.<region>.gcp.elastic.cloud:443 \ --from-literal=secret-token=<your-secret-or-api-key>Apply the Job (it uses the published
mockholm/ci-apm-traceimage):kubectl apply -f k8s.yml -n defaultWatch the output:
kubectl logs job/ci-apm-trace -n default -fDelete the Job when done:
kubectl delete -f k8s.yml -n default
The k8s profile picks up the pod name, namespace, node, and IP from the
downward API env vars set in k8s.yml, and labels the trace with
ci_provider: k8s. To run a single step instead of the full lifecycle, see the
cli:k8s / pre:k8s / main:k8s / post:k8s tasks in the
Taskfile.
Taskfile
The repository ships a Taskfile.yml (go-task) that wraps the
CLI for local runs and CI orchestration. It assumes the ci-apm-trace binary is
installed globally (npm) and the container image is the published
mockholm/ci-apm-trace.
| Task | What it runs |
| --- | --- |
| task cli PROFILE=<p> | one-shot run with the given profile |
| task pre/main/post PROFILE=<p> | the split lifecycle, printing APM_* hand-off vars from pre |
| task cli:docker, pre:docker, main:docker, post:docker | the same runs inside the Docker image |
| task cli:k8s, pre:k8s, main:k8s, post:k8s | the same runs as Kubernetes Pods |
| task k8s:apply, k8s:logs, k8s:delete | manage the k8s.yml Job |
| task test, test:proxy, test:dotenv | smoke tests against the local source build |
| task docker:build | build/tag the image locally |
| task publish:github VERSION=x, publish:azure | release tasks (see Publishing) |
Example — drive the client with the task profile:
export ELASTIC_APM_SERVER_URL=...
export ELASTIC_APM_SECRET_TOKEN=...
task pre PROFILE=task TRACE_NAME=my-pipeline # exports APM_*
task main PROFILE=task TRACE_NAME=my-pipeline
task post PROFILE=task TRACE_NAME=my-pipelineThe pre output is a list of export APM_* lines; source them (eval
"$(task pre ...)") or persist them in your CI's environment so main/post
can pick them up.
Client pipelines
This repository also contains ready-made pipelines that run the client against a real APM Server — useful as templates for your own CI.
GitHub Actions
Three manually-triggered workflows (dispatch Actions in the GitHub UI), one per profile:
| Workflow | Profile | How the client runs |
| --- | --- | --- |
| .github/workflows/client-npm.yml | npm | npm install -g @mockholm/ci-apm-trace, then ci-apm-trace pre/main/post |
| .github/workflows/client-task.yml | task | installs go-task, npm install -g @mockholm/ci-apm-trace, then task pre/main/post PROFILE=task |
| .github/workflows/client-docker.yml | docker | docker run mockholm/ci-apm-trace pre/main/post |
Each takes the inputs trace-name (default npm/task/docker), fail
(end the trace as a failure), and debug. Configure these in the repository
before running:
- Variable
ELASTIC_APM_SERVER_URL— your APM Server URL - Secret
ELASTIC_APM_SECRET_TOKEN— your secret token or API key
Azure Pipelines
Three pipelines in .azure-pipelines/, run manually (trigger: none):
| Pipeline | Profile | How the client runs |
| --- | --- | --- |
| .azure-pipelines/npm.yml | npm | npm install -g @mockholm/ci-apm-trace, then ci-apm-trace pre/main/post |
| .azure-pipelines/task.yml | task | installs go-task, global npm install, then task pre/main/post PROFILE=task |
| .azure-pipelines/docker.yml | docker | docker run mockholm/ci-apm-trace pre/main/post |
Set these pipeline variables (project Pipelines > Edit > Variables):
ELASTIC_APM_SERVER_URL(plain)ELASTIC_APM_SECRET_TOKEN(secret)traceName(optional, defaults tonpm/task/docker)
Team City
In the example here Team city uses the npm client.
Params
| Key | Type | Value | |-------------------------------|-------------|-------------------------------------------------------------------------------------------------| | env.ELASTIC_APM_SECRET_TOKEN | password | credentialsJSON:52e0036f-2008-4145-9122-c11f392bdd9a | | env.PLATFORM_NAME | parameter | team-city | | env.TRACE_NAME | parameter | teamcity-tracer | | env.ELASTIC_APM_SERVER_URL | parameter | https://my-observability-project-d54a32.apm.europe-west2.gcp.elastic.cloud:443 |
Here is the kotlin code:
package buildTypes
import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.BuildType
import jetbrains.buildServer.configs.kotlin.buildSteps.nodeJS
import jetbrains.buildServer.configs.kotlin.buildSteps.script
import jetbrains.buildServer.configs.kotlin.ui.*
object Amp : BuildType({
id("RootProjectId_Project_Environments_Development_Amp")
name = "Amp"
// params
params {
password("env.ELASTIC_APM_SECRET_TOKEN", "credentialsJSON:52e0036f-2008-4145-9122-c11f392bdd9a")
param("env.PLATFORM_NAME", "team-city")
param("env.TRACE_NAME", "teamcity-tracer")
param("env.ELASTIC_APM_SERVER_URL", "https://my-observability-project-d54a32.apm.europe-west2.gcp.elastic.cloud:443")
}
// git repo
vcs {
root(RelativeId("RootProjectId_Project_Environments_Development_HttpsGithubComKesi03apmTaskRefsHeadsMain"))
}
// steps
steps {
// 1. install npm client as global
nodeJS {
name = "install"
id = "install"
shellScript = "npm install -G @mockholm/ci-apm-trace"
}
// 2. run pre trace
nodeJS {
name = "pre-trace"
id = "pre_trace"
shellScript = """
npx ci-apm-trace pre --trace-name "%env.TRACE_NAME%" --ci_platform "%env.PLATFORM_NAME%" | tee apm.env
grep '^export APM_' apm.env | while IFS='=' read -r k v; do
key=${'$'}(echo "${'$'}k" | cut -d' ' -f2)
echo "##teamcity[setParameter name='env.${'$'}{key}' value='${'$'}{v}']"
done
""".trimIndent()
}
// 3. version
nodeJS {
name = "version"
id = "version"
shellScript = "npx ci-apm-trace --version"
}
// 4 sleep
script {
name = "sleep"
id = "sleep"
scriptContent = """
sleep 10
echo "done sleeping"
""".trimIndent()
}
// 5. run the span trace
nodeJS {
name = "main"
id = "main"
shellScript = """npx ci-apm-trace main --trace-name "%env.TRACE_NAME%" --ci_platform "%env.PLATFORM_NAME%""""
}
// 6. end
nodeJS {
name = "post"
id = "post"
shellScript = """npx ci-apm-trace post --trace-name "%env.TRACE_NAME%" --ci_platform "%env.PLATFORM_NAME%""""
}
}
})How the pipelines hand off state
Each pipeline runs pre first, captures the printed export APM_* lines, and
persists them ($GITHUB_ENV on GitHub, ##vso[task.setvariable] on Azure) so
main and post can continue the same trace:
pre— generates the trace/transaction/span IDs and prints theAPM_*varsmain— records aMain Task Executionspan under the same tracepost— closes the trace (transaction, error on failure, metrics), usingJOB_STATUS/failto decide success or failure
Exit codes
| Code | Meaning |
| --- | --- |
| 0 | Trace ended successfully |
| 1 | Trace ended as a failure (--fail or an error occurred) |
Proxy support
The CLI honors the standard HTTP_PROXY/HTTPS_PROXY and NO_PROXY environment variables when sending traces to the APM Server:
| Variable | Effect |
| --- | --- |
| HTTP_PROXY / http_proxy | Proxy used for http:// APM Server URLs |
| HTTPS_PROXY / https_proxy | Proxy used for https:// APM Server URLs (falls back to HTTP_PROXY) |
| NO_PROXY / no_proxy | Comma-separated hosts that bypass the proxy (*, .example.com subdomains, and host:port are supported) |
This routes APM Server traffic through a corporate proxy or egress gateway without extra configuration:
export HTTPS_PROXY=http://proxy.corp.example:3128
export NO_PROXY=localhost,127.0.0.1,.internal.example
ci-apm-trace --trace-name release --build-id 42