ScreenshotNeo

BlogHow-to

What Is a Dockerfile and How Do You Create a Docker Image?

Learn what a Dockerfile does, write one, build an image, use build context, add multi-stage builds, and troubleshoot common errors.

By the ScreenshotNeo team1 October 20267 min read

Direct answer: A Dockerfile is a text file containing the instructions Docker uses to assemble an image. Put it with the files required for the build, then run docker build -t my-app:1.0 .. The final . is the build context: the directory whose files Docker may read. Use a .dockerignore file to exclude irrelevant or sensitive files. For applications that compile or package code, use multiple build stages and copy only the runtime artifacts into the final stage.

1. What a Dockerfile contains

Docker reads Dockerfile instructions from top to bottom. FROM selects a base image and starts a build stage. Common instructions then establish a working directory, copy files into the image, run build commands, define environment or metadata, and specify the default process.

Instruction Purpose Typical use
FROM Initializes a stage from a base image. FROM node:22
WORKDIR Sets the working directory for following instructions and the default process. WORKDIR /app
COPY Copies files from the build context into the image. COPY package*.json ./
RUN Executes a command while building the image. RUN npm install
CMD Provides the default command when a container starts. CMD ["npm", "start"]

Docker’s Dockerfile reference documents the complete instruction set and syntax.

2. Create a first Docker image

Step 1: Create a project directory

mkdir my-app
cd my-app

Place your application files in this directory. The directory will be the build context.

Step 2: Add a Dockerfile

Create a file named exactly Dockerfile with no filename extension:

FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "start"]

This template is illustrative. Use the base image and dependency command that match your application and lockfile. Your application must also start successfully with the command in CMD.

Step 3: Exclude files from the build context

Create .dockerignore in the context root:

node_modules
.git
.env
*.log
coverage

The Docker builder removes matching paths before transferring a local directory context. Keep secrets, local dependencies, logs, and generated output out of the context unless the build genuinely needs them. Docker also supports Dockerfile-specific ignore files; when both are present, the Dockerfile-specific file takes precedence. See the build context documentation.

Step 4: Build and tag the image

docker build -t my-app:1.0 .

-t my-app:1.0 assigns a repository name and tag. The final dot supplies the context. Docker can only copy files available inside that context.

Step 5: Inspect and run it

docker image ls my-app
docker run --rm my-app:1.0

If the application listens on a port, publish that port when running the container. For example, an application listening on port 3000 inside the container can be started with:

docker run --rm -p 3000:3000 my-app:1.0

3. Build context: the boundary behind the final dot

In docker build -t my-app:1.0 ., the last argument is not optional decoration. It tells Docker which directory supplies files to the build. A COPY source path is resolved relative to that context, so a Dockerfile cannot copy an arbitrary file from a parent directory outside it.

To use a different Dockerfile name or location, pass the Dockerfile option supported by your Docker CLI and keep the context argument explicit. Check the current docker image build reference for the exact invocation available in your version.

4. Single-stage versus multi-stage builds

A single-stage Dockerfile is easiest to understand and can suit a small application. A multi-stage Dockerfile uses more than one FROM instruction: one stage compiles or packages the application, and a later stage contains only what is needed to run it.

# Build stage
FROM node:22 AS build
WORKDIR /src
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Runtime stage
FROM node:22
WORKDIR /app
COPY --from=build /src/package*.json ./
RUN npm install --omit=dev
COPY --from=build /src/dist ./dist
CMD ["node", "dist/server.js"]

Adjust paths and commands to your project. The important pattern is selective copying from build into the runtime stage. Compilers and intermediate files remain in the earlier stage instead of automatically becoming part of the final image. Docker’s multi-stage build guide explains this model.

5. Make builds repeatable and maintainable

  • Choose a base image appropriate for the runtime and application.
  • Copy dependency manifests before application source when that matches your dependency workflow; this keeps the file structure understandable and can improve cache reuse when earlier inputs have not changed.
  • Use the lockfile-aware install command supported by your package manager.
  • Keep secrets out of the Dockerfile and build context.
  • Use multi-stage builds when compilation tools are not needed at runtime.
  • Tag images with meaningful versions such as 1.0 rather than relying only on an ambiguous moving tag.
  • Review Docker’s building best practices for cache and image-organization guidance that fits your application.

6. Tag, share, and publish an image

Building creates a local image. Sharing it is a separate operation: tag the image for the registry and then publish it.

docker build -t my-app:1.0 .
docker tag my-app:1.0 REGISTRY_HOST/ACCOUNT/my-app:1.0
docker push REGISTRY_HOST/ACCOUNT/my-app:1.0

Replace the registry host and account path with the destination used by your project. Docker’s walkthrough covers the build, tag, and publish sequence without implying that docker build publishes automatically: build, tag, and publish an image.

7. Troubleshooting common Dockerfile and build errors

Symptom Likely cause Fix
COPY failed: file not found The source is outside the build context, misspelled, or excluded by .dockerignore. Check the path relative to the context root, run the build from the intended directory, and inspect ignore patterns.
docker build cannot find the Dockerfile The file has an extension, a different name, or the command is run from another directory. Confirm the file is named Dockerfile, use the appropriate Dockerfile option for another name, and pass the correct context.
Dependency installation fails The base image lacks the expected runtime, manifests are missing, or the install command does not match the lockfile. Use a compatible base image, copy the required manifest and lockfile, and use the package manager’s lockfile-aware command.
Container exits immediately The default command finished, is missing, or failed at startup. Run the image while observing logs, verify CMD, and ensure the application has a long-running foreground process.
Port is unreachable The container port was not published, or the application listens on a different port. Use -p host:container and align it with the port used by the application.
Image is unexpectedly large Build tools, dependency caches, source trees, or generated files remain in the final stage. Add a suitable .dockerignore and use a multi-stage build that copies only runtime artifacts.
Build context transfer is slow Large directories are being sent to the builder. Exclude dependencies, version-control data, logs, test output, and other files not required to build.
Build behaves differently on another machine Base image tags, dependency inputs, platform, or builder behavior differ. Use explicit version tags where appropriate, commit lockfiles, and check the Docker and BuildKit/Buildx versions in use.

Current Docker CLI builds generally use Buildx and BuildKit by default. Legacy behavior and feature differences can matter in particular environments, including some Windows-container cases; consult the current CLI reference for your platform and Docker version.

8. Performance, reliability, and cost considerations

Build performance

  • Keep the context small with .dockerignore; fewer files means less input to transfer and inspect.
  • Order instructions so stable dependency inputs are handled before frequently changing source files when that matches your application.
  • Use multi-stage builds to separate compilation from runtime packaging.

Runtime reliability

  • Make the startup command explicit and ensure it runs in the foreground.
  • Keep runtime dependencies and generated artifacts in the final stage.
  • Verify that the application binds to the interface and port expected by its container environment.

Cost and storage

Dockerfile choices affect build time, transferred context size, local disk use, registry storage, and image-pull time. A smaller final stage and a focused context reduce material that must be stored or moved, but the right trade-off depends on the application and its build process.

9. Or skip the browser setup

If your Docker workflow needs website screenshots for documentation, visual checks, or generated assets, ScreenshotNeo provides a website screenshot API and MCP server. It accepts one GET request and returns a PNG, JPEG, WebP, or PDF. Cookie and consent banners are accepted before capture, then more than 60 known consent platforms, newsletter popups, and chat widgets are removed. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.

See the ScreenshotNeo API documentation for options such as full-page capture, element selectors, device and retina settings, dark mode, custom CSS and JavaScript, waits, request blocking, headers, cookies, caching, signed links, asynchronous jobs, bulk capture, and PDF output.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://docs.docker.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://docs.docker.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://docs.docker.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also includes an MCP server so Claude, Cursor, and other MCP clients can use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

10. FAQ

Does the file have to be named Dockerfile?

No. Docker supports selecting another Dockerfile name with the relevant CLI option, but the conventional name is Dockerfile.

Does every Dockerfile need a CMD?

No, but an image intended to run an application needs a clear startup command supplied by the image or by docker run.

Is an image the same thing as a container?

No. The Dockerfile builds an image; running that image creates a container.

Why use a registry?

A registry provides a destination from which other machines or deployment systems can retrieve tagged images.

When should I use multiple stages?

Use them when building requires tools or files that the application does not need at runtime, or when you want a deliberately smaller runtime image.