Dockerfile by Example: Source Code Inclusion
This sample code focuses on the COPY instruction specifically for application code, highlighting the importance of .dockerignore and user permissions.
Code
FROM node:18-alpine
WORKDIR /app
# Create a non-root user (Node image comes with 'node' user)
# It is best practice to run apps as non-root
USER node
# Copy package files with correct ownership
COPY --chown=node:node package*.json ./
RUN npm ci --only=production
# Copy application source code
# We use .dockerignore to exclude node_modules, .git, etc.
COPY --chown=node:node . .
CMD ["node", "index.js"]Explanation
Including source code via COPY . . is a standard pattern, but it requires careful management of context and permissions. A .dockerignore file is essential to exclude local artifacts like node_modules, .git, and secrets. Without it, these files are copied into the image, potentially overwriting installed dependencies with architecture-mismatched binaries or leaking sensitive information.
Security best practices for source inclusion:
- Run applications as a non-root user to limit potential impact of vulnerabilities
- Use
COPY --chownto set ownership during copy, avoiding recursive chown later - Exclude development artifacts using
.dockerignore - Install production-only dependencies to minimize image size
By default, Docker containers run as root. Switching to a non-privileged user (like node) using the USER instruction drastically reduces the attack surface. However, files copied before the USER instruction are owned by root. The --chown flag on COPY ensures that the application user has the necessary read/write permissions on the source code without requiring a separate, slow, and layer-bloating RUN chown command.
Code Breakdown
USER node switches execution to unprivileged user.COPY --chown=node:node ensures files are owned by the user running the app.npm ci --only=production installs deterministic dependencies, skipping dev tools.COPY . . relies on .dockerignore to filter out unwanted files.
