Consolidated reference for building project artifacts locally. For CI job details and exact CI command equivalents, see compile-artifacts.md.
/rust/cargo/ in CI images is root-owned. When running dockerh as non-root
(i.e. without --root), Cargo cannot write to it. Override it:
- With
--overlayfs(recommended): pass-e CARGO_HOME=/project/dd-trace-php/.cache/cargo. - Without overlayfs, place it in one of the cache bind mounted directories:
-e CARGO_HOME=/project/dd-trace-php/tmp/cargo_home.
This affects build-sidecar.sh, profiler builds, and any other Rust build that
does not use --root. First local run downloads all crates from scratch.
Before any build, ensure the relevant submodules are initialised (see also ../general.md section 4):
# Tracer extension (ddtrace.so) — needs libdatadog
git submodule update --init libdatadog
# Appsec extension or helper — needs these additionally
git submodule update --init \
appsec/third_party/libddwaf \
appsec/third_party/msgpack-c \
appsec/third_party/cpp-base64
# Appsec helper rust — needs libddwaf-rust
git submodule update --init --recursive \
appsec/third_party/libddwaf-rustOn centos-7 images, PHP variants are version-prefixed: 8.3,
8.3-debug, 8.3-zts. On bookworm images, variants are bare
names: nts, debug, zts, nts-asan, debug-zts-asan.
Build scripts that call switch-php internally (e.g.
compile_extension.sh, build-tracing.sh, build-appsec.sh,
build-profiler.sh) handle this themselves and need --root (not
--php) so they can modify /usr/local/bin/ symlinks.
The centos-7 base ships GCC 4.8, which is too old for C++17 code (appsec extension). On centos-7 images, activate GCC 7 first:
source /opt/rh/devtoolset-7/enableThis is needed for build-appsec.sh on centos-7 but not on bookworm
(which has a modern GCC).
make links Rust inline and produces a self-contained ddtrace.so.
make static splits the Rust library out into .a archives (used by
the package pipeline's two-phase build). For local testing, always use
make unless you specifically need the split build.
To validate Rust changes in the libdatadog/ submodule, run from inside it:
cd libdatadog
cargo +nightly fmt --all --quiet && \
cargo clippy --workspace --all-targets --all-features -- -D warningsA rustc ≥1.87 toolchain override is active in the repo, so plain cargo clippy
picks up the correct toolchain — do NOT force +stable (the default stable is
older) or a pinned +1.87.0, and do not lint per-crate with -p. Only fmt
needs +nightly.
Used before running tracer unit tests, .phpt tests, etc.:
.claude/ci/dockerh --cache tracer-8.3-debug --overlayfs --php debug \
datadog/dd-trace-ci:php-8.3_bookworm-6 -- bash -c '
set -e
git submodule update --init libdatadog
make -j$(nproc) all
make install
'make install (not make install_all) suffices for test_c and
test_opcache. PHPUnit jobs need these additional steps after
make all:
make install_all
composer update --no-interaction
make generateSee tracer-unit-tests.md for full PHPUnit run commands.
Used when building packages for system-tests. Targets GLIBC 2.17 for maximum compatibility:
.claude/ci/dockerh --cache systest-82 --php 8.2 \
datadog/dd-trace-ci:php-8.2_centos-7 -- \
bash -c 'export CARGO_HOME=$PWD/tmp/cargo_home; make -j$(nproc)'First build: ~20 min (Rust sidecar). Incremental (C-only): ~1 min.
For -O0 debugging (fewer <optimized out> in gdb):
CFLAGS="-std=gnu11 -O0 -g" make -j$(nproc)Reproduces the compile extension: debug CI job exactly:
.claude/ci/dockerh --cache tracer-8.3-debug --overlayfs --root \
datadog/dd-trace-ci:php-8.3_bookworm-6 \
-e CI_COMMIT_SHA=$(git rev-parse HEAD) \
-e CI_COMMIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) \
-e SHARED=1 \
-- bash .gitlab/compile_extension.shSee compile-artifacts.md for all CI compile job variants (ASAN, ZTS, package pipeline, etc.).
Use a separate cache from the normal debug build. COMPILE_ASAN=1
enables -fsanitize=address in the Rust sidecar.
.claude/ci/dockerh --cache tracer-8.3-asan --overlayfs \
--php debug-zts-asan \
datadog/dd-trace-ci:php-8.3_bookworm-6 -- bash -c '
set -e
export COMPILE_ASAN=1
make -j$(nproc) all
'Needs devtoolset-7 for C++17 support:
.claude/ci/dockerh --cache compile-appsec-8.3-gnu --overlayfs --root \
datadog/dd-trace-ci:php-8.3_centos-7 \
-e CI_COMMIT_SHA=$(git rev-parse HEAD) \
-e CI_COMMIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) \
-- bash -c 'PHP_VERSION=8.3 bash .gitlab/build-appsec.sh'(build-appsec.sh sources devtoolset-7 internally on centos-7.)
Uses cmake directly with test flags. This is a different build from the CI release build above (builds test targets, uses libc++):
mkdir -p appsec/build && cd appsec/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DDD_APPSEC_BUILD_HELPER=OFF \
-DCMAKE_CXX_FLAGS="-stdlib=libc++" \
-DCMAKE_CXX_LINK_FLAGS="-stdlib=libc++" \
-DDD_APPSEC_TESTING=ON
make -j$(nproc) xtestFor ASAN, add -DENABLE_ASAN=ON to cmake. See
appsec-native-tests.md for full details.
The tarball needs two helper binaries in appsec_$(uname -m)/:
libddappsec-helper.so (C++) and libddappsec-helper-rust.so
(Rust), plus appsec/recommended.json.
Image is on Docker Hub. Output: appsec_$(uname -m)/libddappsec-helper-rust.so.
git submodule update --init --recursive \
appsec/third_party/libddwaf-rust
.claude/ci/dockerh --cache compile-appsec-helper-rust --overlayfs \
datadog/dd-appsec-php-ci:nginx-fpm-php-8.5-release-musl \
-e CI_COMMIT_SHA=$(git rev-parse HEAD) \
-e CI_COMMIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) \
-- bash .gitlab/build-appsec-helper-rust.shThe CI image (nginx_musl_toolchain) lives at
registry.ddbuild.io/images/mirror/b1o7r7e0/nginx_musl_toolchain
(not on Docker Hub). Transfer it via
docker save ... | ssh HOST docker load if needed. Output:
appsec_$(uname -m)/libddappsec-helper.so + recommended.json.
git submodule update --init \
appsec/third_party/libddwaf \
appsec/third_party/msgpack-c \
appsec/third_party/cpp-base64
.claude/ci/dockerh --cache compile-appsec-helper-cpp --overlayfs \
registry.ddbuild.io/images/mirror/b1o7r7e0/nginx_musl_toolchain \
-e CI_COMMIT_SHA=$(git rev-parse HEAD) \
-e CI_COMMIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) \
-- bash .gitlab/build-appsec-helper.shCARGO_TARGET_DIR must be set explicitly (see
github-actions-profiler.md for why):
dockerh --cache profiler-8.3-nts --php nts \
datadog/dd-trace-ci:php-8.3_bookworm-6 -- bash -c '
export CARGO_TARGET_DIR=/project/dd-trace-php/target
cd profiling && cargo rustc --features=trigger_time_sample \
--profile profiler-release --crate-type=cdylib
'Bookworm is too recent for binary compatibility purposes.
build-profiler.sh takes two arguments: the output directory prefix
and the thread safety mode (nts or zts). It calls switch-php
internally, so use --root (not --php). The output prefix must
match the directory layout expected by generate-final-artifact.sh:
datadog-profiling/{triplet}/lib/php/{PHP_API}/.
Build one PHP version at a time (each centos-7 image ships one
version). For a single version (e.g. 8.2, ABI 20220829):
.claude/ci/dockerh --cache compile-profiler-8.2-gnu --overlayfs \
--root \
datadog/dd-trace-ci:php-8.2_centos-7 \
-e CI_COMMIT_SHA=$(git rev-parse HEAD) \
-e CI_COMMIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) \
-- bash -c 'PHP_VERSION=8.2 bash .gitlab/build-profiler.sh \
datadog-profiling/x86_64-unknown-linux-gnu/lib/php/20220829 nts'.claude/ci/dockerh --cache compile-sidecar-gnu --overlayfs \
datadog/dd-trace-ci:php-8.1_centos-7 \
-e CI_COMMIT_SHA=$(git rev-parse HEAD) \
-e CI_COMMIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) \
-e CARGO_HOME=/project/dd-trace-php/.cache/cargo \
-- bash -c 'HOST_OS=linux-gnu bash .gitlab/build-sidecar.sh'# linux-gnu
.claude/ci/dockerh --cache compile-loader-gnu --overlayfs \
datadog/dd-trace-ci:php-8.3_centos-7 \
-e CI_COMMIT_SHA=$(git rev-parse HEAD) \
-e CI_COMMIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) \
-- bash -c 'HOST_OS=linux-gnu bash .gitlab/build-loader.sh'
# linux-musl (requires --root for apk add)
.claude/ci/dockerh --cache compile-loader-musl --overlayfs --root \
datadog/dd-trace-ci:php-compile-extension-alpine-8.3 \
-e CI_COMMIT_SHA=$(git rev-parse HEAD) \
-e CI_COMMIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) \
-- bash -c 'HOST_OS=linux-musl bash .gitlab/build-loader.sh'generate-final-artifact.sh assembles a release tarball from
compiled artifacts. It takes three arguments:
generate-final-artifact.sh VERSION OUTPUT_DIR PROJECT_ROOT
VERSION— version string (from theVERSIONfile)OUTPUT_DIR— where to write the tarball (e.g.build/packages)PROJECT_ROOT— repo root (for PHP stub files insrc/,ext/)
Set TRIPLET to limit assembly to one platform (e.g.
x86_64-unknown-linux-gnu). Without it, the script tries all
platforms and fails if artifacts are missing.
Prerequisites: the script expects these directories to contain
compiled .so files:
extensions_$(uname -m)/— ddtrace extensions (ddtrace-{API}[-zts|-debug|-debug-zts].so)appsec_$(uname -m)/— appsec extensions (ddappsec-{API}[-zts].so) + helpers (libddappsec-helper.soandlibddappsec-helper-rust.so) +recommended.jsondatadog-profiling/{triplet}/lib/php/{API}/— profiler extensions
Missing files cause hard cp failures. This means that we need to build (or
download from CI) all these individual artifacts. This is rarely desirable when
testing locally. See the section "Slim package with debug binaries" for a more
practical alternative when locally producing artifacts from some jobs, like
system tests.
Naming conventions differ by platform. GNU/glibc extensions use
bare names (ddtrace-{API}.so, ddtrace-{API}-zts.so) plus
-debug and -debug-zts variants (4 total). Alpine/musl extensions
use the -alpine suffix (ddtrace-{API}-alpine.so,
ddtrace-{API}-alpine-zts.so) and have no -debug variants
(2 total). Appsec follows the same pattern (no -debug for either
platform).
The script only needs basic shell tools (cp, tar, mkdir).
The php_fpm_packaging image is used in CI because the same job
also runs nfpm for .deb/.rpm/.apk, but any image with bash works
for tarball assembly alone.
docker run --rm -v "$(pwd)":/work -w /work php:8.2-cli \
bash -c 'make build/packages/datadog-setup.php VERSION=$(cat VERSION)'The php_fpm_packaging image has entrypoint ["bash"], so pass
-c '...' directly (not bash -c '....').
.claude/ci/dockerh --cache pkg-amd64-gnu --overlayfs \
datadog/dd-trace-ci:php_fpm_packaging -- -c '
set -e
TRIPLET=x86_64-unknown-linux-gnu \
./tooling/bin/generate-final-artifact.sh \
$(<VERSION) "build/packages" "${PWD}"
'
.claude/ci/docker-upper-cp dd-ci-pkg-amd64-gnu \
build/packages build/packagesOutput in build/packages/:
dd-library-php-<version>-x86_64-linux-gnu.tar.gzdatadog-setup.php
To also build .deb/.rpm packages (full CI equivalent), add the
fpm targets before the tarball assembly in the same dockerh session:
.claude/ci/dockerh --cache pkg-amd64-gnu --overlayfs \
datadog/dd-trace-ci:php_fpm_packaging -- -c '
set -e
make -j 4 .rpm.x86_64 .deb.x86_64 .tar.gz.x86_64
TRIPLET=x86_64-unknown-linux-gnu \
./tooling/bin/generate-final-artifact.sh \
$(<VERSION) "build/packages" "${PWD}"
'
.claude/ci/docker-upper-cp dd-ci-pkg-amd64-gnu \
build/packages build/packagesFor Alpine/musl, the target is .apk.x86_64 (or .apk.aarch64).
The php_fpm_packaging image has no arm64 variant. Use
ubuntu:24.04 for tarball assembly only. In CI, all packaging runs
on amd64 runners — arm64 .deb/.rpm/.apk packages are
cross-built on amd64 (architecture is just a metadata field in fpm).
.claude/ci/dockerh --cache pkg-arm64-gnu --overlayfs \
ubuntu:24.04 \
-e TRIPLET=aarch64-unknown-linux-gnu \
-- bash -c '
set -e
./tooling/bin/generate-final-artifact.sh \
$(<VERSION) "build/packages" "${PWD}"
'
.claude/ci/docker-upper-cp dd-ci-pkg-arm64-gnu \
build/packages build/packagesgenerate-ssi-package.sh assembles the SSI (loader) tarball. Unlike
generate-final-artifact.sh, it runs objcopy --only-keep-debug and
strip on every .so — empty stub files will fail. You need real
compiled artifacts.
The script reads from standalone_$(uname -m)/ (standalone .so
files from compile tracing extension), not extensions_$(uname -m)/
(which has .a archives for the link phase).
For aarch64, the script uses cross-tools (aarch64-linux-gnu-objcopy,
aarch64-linux-gnu-strip). These are available in the
php_fpm_packaging image but not on a native arm64 host. All
package loader CI jobs run on amd64 runners, even for arm64
packages.
.claude/ci/dockerh --cache pkg-loader --overlayfs \
datadog/dd-trace-ci:php_fpm_packaging \
-e ARCHITECTURE=x86_64 \
-- -c '
set -e
./tooling/bin/generate-ssi-package.sh $(<VERSION) build/packages
'
.claude/ci/docker-upper-cp dd-ci-pkg-loader \
build/packages build/packagesSet ARCHITECTURE=aarch64 for arm64 (still runs in the amd64
php_fpm_packaging image, using cross-tools).
tooling/bin/build-debug-artifact builds a tarball containing only the PHP
version you need — no stubs, no generate-final-artifact.sh. It
uses the same centos-7 images as the package pipeline and assembles
the tarball directly in the dd-library-php/ layout that
datadog-setup.php expects.
Note: this produces debug (unoptimized) binaries, which differ from the release binaries built by CI. They are suitable for development and troubleshooting but not for performance testing.
# Tracer only (gnu, x86_64, PHP 8.2, NTS)
tooling/bin/build-debug-artifact gnu-x86_64-8.2-nts
# Tracer + appsec (extension + both helpers) + profiler
tooling/bin/build-debug-artifact gnu-x86_64-8.2-nts --appsec --profiler
# Musl/arm64 variant, custom output directory (preferred if the location is somewhere else)
tooling/bin/build-debug-artifact musl-aarch64-8.2-nts /tmp/outAll products build in parallel. Build logs go to a temporary directory printed at the start.