Copy a directory with Dockerfile ADD or COPY

Understand build-context paths, directory contents, archive extraction, ownership, and what a multi-stage runtime image must include.

Best practices
Illustration of container operations and troubleshooting

Both ADD and COPY can copy a directory's contents from a build context into an image. For an ordinary local copy, prefer COPY; use ADD when you deliberately need its additional archive or remote-source behavior.

Copy the directory contents

FROM alpine:3.22
COPY ./myapp /app/

With a context containing myapp/config.yml, the image receives /app/config.yml, not /app/myapp/config.yml. To create that additional directory level, choose /app/myapp/ as the destination. ADD ./myapp /app/ has the same ordinary-directory behavior.

Source paths are relative to the build context, usually the final path argument to docker build, rather than automatically relative to the Dockerfile's own directory. A Dockerfile selected with -f does not change that rule. Ordinary source paths cannot escape the context to read arbitrary parent directories.

A source directory's trailing slash does not create another directory level. Destination trailing slashes matter particularly for file copies: /app/config can name a file, while /app/config/ denotes a directory. Existing contents can be merged, and incompatible file/directory destinations can fail.

Choose ADD for its extra behavior intentionally

Input COPY ADD
Local files or directories Copies them Copies them
Recognized local tar archive Copies the archive bytes Normally extracts the archive
HTTP or Git source Not directly as an ordinary source URL Supports remote sources with applicable options
Earlier build stage or named context Supports --from Use the capability appropriate to the source and builder

Archive detection depends on content, not just a filename extension. A remotely downloaded tar file is not automatically treated identically to a local archive. Check the exact source type and supported Dockerfile frontend options instead of assuming every ADD decompresses every input.

For HTTP resources, supported ADD --checksum=sha256:... options can verify content; Git sources can be pinned to a commit. A checksum verifies the selected content, not whether the publisher is trustworthy. Replacing a verified download with curl | tar is not automatically safer.

Control what enters the image

A .dockerignore file can exclude local dependencies, repository metadata, and logs:

node_modules
.git
*.log

Ensure it does not exclude actual build inputs. To set Linux ownership during copying:

COPY --chown=1000:1000 ./myapp /app/

Numeric IDs do not require name lookup. Names such as node:node must resolve through the target image's user/group files. Ownership alone does not guarantee access: file modes, parent-directory access, the runtime user, and mounts that hide image paths also matter. --chown is not a Windows-container ownership mechanism.

Copy build output into a runtime image

FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-slim
WORKDIR /app
COPY --from=build --chown=node:node /app/dist ./dist
USER node
CMD ["node", "dist/server.js"]

This assumes a committed lockfile, a build script, and a dist output containing everything required at runtime. If the output imports external packages, install compatible production dependencies in the runtime stage and include required assets. Native modules may require compatible OS libraries and architecture across stages. Smaller images and non-root execution need application-specific validation.

Investigate unexpected build results

COPY/ADD cache decisions depend on relevant input content and metadata; file modification time alone is not a reliable cache invalidation trigger. If a changed file does not appear, check the context, ignore rules, selected stage, actual built image, and runtime mounts before trying --no-cache. Disabling cache cannot include a file excluded from the context.

See Dockerfile ADD and COPY, build contexts, and build cache invalidation.

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting