Extract build artifacts and incident evidence from a container
Identify the exact container, understand docker cp path and ownership rules, avoid inconsistent live copies, verify the bytes, and prefer BuildKit exporters for CI artifacts.
A CI build succeeds inside a container, but the binary exists only at /out/service. In another incident, logs must be collected before the container is removed. docker cp can copy from a running or stopped container without installing SSH or adding a volume. Correct use depends on container identity, source path semantics, consistency and ownership.
Pin the exact container before copying
docker ps -a --no-trunc --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}'
container_id=$(docker inspect --format '{{.Id}}' build-job-42)
image_id=$(docker inspect --format '{{.Image}}' "$container_id")
printf 'container=%s image=%s\n' "$container_id" "$image_id"
Names can be reused after removal. Record the full ID, image digest, host and build or incident identifier. A stopped container is valid input for docker cp, which is useful after a build process exits.
Copy one file to a prepared destination
install -d -m 0750 ./artifacts
docker cp "$container_id":/out/service ./artifacts/service
file ./artifacts/service
sha256sum ./artifacts/service
The colon separates the container from its path. Container paths are interpreted relative to /, so the leading slash is optional, though keeping it improves readability. Local relative paths are resolved from the shell’s current directory.
Know whether you are copying a directory or its contents
# Creates or populates ./evidence/app-logs according to destination state
docker cp "$container_id":/var/log/app ./evidence/
# Copies the contents of app, not the app directory wrapper
docker cp "$container_id":/var/log/app/. ./evidence/app-logs/
Destination existence and trailing /. affect the result much like Unix cp -a. Docker does not create missing parent directories for the destination. Test path shape with a small fixture instead of discovering extra nesting in a release job.
Inspect the source without mutating it
docker container exec "$container_id" sh -c 'ls -ld /out /out/service; sha256sum /out/service'
docker inspect "$container_id" --format '{{range .Mounts}}{{println .Type .Source "->" .Destination}}{{end}}'
If the container is stopped or shell-less, use docker export/docker cp and image metadata rather than starting it solely for inspection. Resolve whether the path belongs to the writable layer, a bind mount, a named volume, tmpfs or secret mount.
Understand ownership behavior
By default, files copied to the host are owned by the user running docker cp. With --archive, Docker attempts to preserve source UID/GID. Numeric container identities may not exist on the host and can create inaccessible artifacts.
docker cp --archive "$container_id":/out/. ./artifacts/
find ./artifacts -maxdepth 2 -printf '%u:%g %m %p\n'
Use archive mode only when identity preservation is required and authorized. For CI deliverables, normalize ownership in a controlled staging directory rather than using broad recursive chown on an unverified path.
Treat symbolic links deliberately
docker exec "$container_id" find /out -maxdepth 2 -type l -ls
docker cp "$container_id":/out/current ./artifacts/current
docker cp --follow-link "$container_id":/out/current ./artifacts/current-target
Docker copies the link itself by default when the source is a symlink. --follow-link copies its target. Following a link can pull data outside the directory you intended, so inspect and bound the source first.
Do not call a live directory copy a consistent snapshot
A running process can modify one file while another is copied. The result may combine states from different moments. For build artifacts, write to a temporary location, fsync where required, compute a manifest, then atomically rename a completed output directory before copying.
# Inside the build stage/process
sha256sum /out/service /out/service.sbom.json > /out/MANIFEST.sha256
mv /out /result-ready
For databases, use database-native backup or snapshot coordination. Copying database files from a running container is not a reliable logical or physical backup.
Capture incident evidence into a new case directory
case_dir="./incident-$(date -u +%Y%m%dT%H%M%SZ)"
install -d -m 0700 "$case_dir"
docker inspect "$container_id" > "$case_dir/inspect.json"
docker logs --timestamps "$container_id" > "$case_dir/container.log" 2>&1
docker diff "$container_id" > "$case_dir/filesystem.diff"
docker cp "$container_id":/app/config "$case_dir/config"
Evidence may contain credentials and personal data. Restrict access, preserve timestamps and source identifiers, compute hashes and follow retention policy. Do not publish docker inspect output without reviewing environment variables and labels.
Stream a tar archive when disk staging is undesirable
docker cp "$container_id":/var/log/app - > "$case_dir/app-logs.tar"
tar -tf "$case_dir/app-logs.tar" | sed -n '1,40p'
sha256sum "$case_dir/app-logs.tar"
When - is the destination, docker cp writes a tar stream to standard output. Redirect binary output directly; do not pipe it through text filters. List members before extraction and defend against unexpected paths and ownership.
Handle pseudo-filesystems separately
Docker documents that some paths under /proc, /sys, /dev, tmpfs and user-created mounts cannot be copied normally. For a readable runtime view, stream an explicitly scoped tar from a running container:
docker exec "$container_id" tar -C /run/app -cf - state.json metrics.txt > "$case_dir/runtime-state.tar"
This requires the container to be running and to contain tar. Avoid privileged access or broad roots merely to make collection convenient.
Verify host and container manifests
docker exec "$container_id" sh -c 'cd /result-ready && sha256sum -c MANIFEST.sha256'
(cd ./artifacts && sha256sum -c MANIFEST.sha256)
A successful copy exit status says the transfer completed, not that the artifact matches the intended build or architecture. Verify checksums, expected filenames, executable format, signature, SBOM and provenance before publishing.
Prefer a BuildKit local exporter for CI outputs
# Dockerfile
FROM toolchain AS build
RUN make -C /src release
FROM scratch AS artifact
COPY --from=build /src/out/ /
# CI
docker buildx build --target artifact --output type=local,dest=./artifacts .
Docker’s local exporter writes the final stage filesystem directly to a host directory. It removes the need to find an intermediate container and makes the artifact stage an explicit build result. For multi-platform builds, outputs are separated by platform by default.
Use docker cp for the cases it fits
| Need | Preferred mechanism |
|---|---|
| Ad hoc file from existing container | docker cp |
| Incident evidence from stopped container | docker cp plus metadata/log capture |
| Repeatable CI build artifacts | BuildKit local or tar exporter |
| Persistent application state | Managed volume/storage workflow |
| Database backup | Database-supported backup/snapshot |
Clean up only after downstream acceptance
test -s ./artifacts/service
sha256sum -c ./artifacts/MANIFEST.sha256
./artifact-smoke-test ./artifacts/service
docker rm "$container_id"
Container removal is irreversible for its writable layer. Confirm artifact upload, hashes, test results and evidence retention before deleting it. Label CI containers with expiry and job identity so cleanup is explicit rather than broad pruning.
docker cp is an extraction tool, not a consistency or provenance system. Pin the container, control path semantics, verify the bytes, and use a build exporter when artifacts are the intended product.
Sources: the solved Stack Overflow question (question by user2668128; accepted answer by creack, CC BY-SA), and Docker’s official docker cp, exporter overview, and local/tar exporter documentation.
Primary source: Review the official reference ↗