Skip to main content

Actor Dockerfile and base images

When developing an Actor on the Apify platform, you can choose from a variety of pre-built Docker images to serve as the base for your Actor. These base images come with pre-installed dependencies and tools, making it easier to set up your development environment and ensuring consistent behavior across different environments.

Base Docker images

Apify provides several Docker images that can serve as base images for Actors. All images come in two versions:

  • latest - This version represents the stable and production-ready release of the base image.
  • beta - This version is intended for testing new features. Use at your own risk.
Pre-cached Docker images

All Apify Docker images are pre-cached on Apify servers to speed up Actor builds and runs. The source code for generating these images is available in the apify-actor-docker repository.

Node.js base images

These images come with Node.js (versions 22, 24, or 26). The latest tag corresponds to the latest LTS version of Node.js.

Every image is published in two flavours. The full image preinstalls the Apify SDK for JavaScript, Crawlee and TypeScript. The -slim variant (e.g. 24-slim, 24-1.60.0-slim) only ships the browser automation library the image is built around (for example puppeteer or playwright), and actor-node:24-slim ships no npm packages at all. Slim images are smaller and faster to pull, and your package.json is the single source of truth for dependency versions.

Use the slim variant unless you have a reason not to. Reach for the full image when you want to run something quickly without maintaining a package.json, or when you rely on the exact preinstalled versions of apify and crawlee.

FROM apify/actor-node-playwright-chrome:24-1.60.0-slim
ImageDescription
actor-nodeSlim Alpine Linux image with only essential tools. Does not include headless browsers.
actor-node-puppeteer-chromeDebian image with Chromium, Google Chrome, and the puppeteer library.
actor-node-playwright-chromeDebian image with Chromium, Google Chrome, and the playwright library.
actor-node-playwright-firefoxDebian image with Firefox and the playwright library .
actor-node-playwright-webkitUbuntu image with WebKit and the playwright library.
actor-node-playwrightUbuntu image with playwright and all its browsers (Chromium, Google Chrome, Firefox, WebKit).
actor-node-playwright-camoufoxDebian image with Camoufox, a Firefox fork hardened against bot detection, and the playwright, camoufox-js and impit libraries.

See the Docker image guide for more details.

Python base images

These images come with Python (version 3.10, 3.11, 3.12, 3.13, or 3.14) and the Apify SDK for Python preinstalled. The latest tag corresponds to the latest Python 3 version supported by the Apify SDK.

ImageDescription
actor-pythonSlim Debian image with only the Apify SDK for Python. Does not include headless browsers.
actor-python-playwrightDebian image with playwright and all its browsers.
actor-python-playwright-camoufoxDebian image with Camoufox, a Firefox fork hardened against bot detection, and the playwright library.
actor-python-seleniumDebian image with selenium, Google Chrome, and ChromeDriver.

Image tag naming convention

Docker image tags follow a consistent naming pattern that allows you to pin specific versions:

Node.js images

For Node.js images, the tag format is:

  • {node-version} - A Node.js version only (e.g., 22, 24, 26)
  • {node-version}-{library-version} - A Node.js version with pinned Playwright/Puppeteer version (e.g., 22-1.52.0)
  • {...}-slim - Any of the above without preinstalled apify, crawlee and typescript (e.g., 24-slim, 22-1.52.0-slim)

Examples:

TagDescription
22Node.js 22 with the Playwright/Puppeteer version that was latest when the image was built
24Node.js 24 with the Playwright/Puppeteer version that was latest when the image was built
22-1.52.0Node.js 22 with Playwright/Puppeteer version 1.52.0 pinned
22-1.52.0-slimSame as 22-1.52.0, but without preinstalled apify, crawlee and typescript
latestLatest LTS Node.js version

Python images

For Python images, the tag format is:

  • {python-version} - A Python version only (e.g., 3.12, 3.13, 3.14)
  • {python-version}-{library-version} - A Python version with pinned Playwright/Selenium version

Available tags

To see all available tags for an image, visit Docker Hub:

You can also query available tags programmatically:

curl -s "https://registry.hub.docker.com/v2/repositories/apify/actor-node-playwright-chrome/tags?page_size=50" | jq '.results[].name'

Version pinning for reproducible builds

For production Actors, pin both the Node.js/Python version and the browser automation library version in your Dockerfile. This ensures reproducible builds and prevents unexpected behavior when new versions are released.

In your Dockerfile, use a fully pinned tag:

# Pin both Node.js 22 and Playwright 1.52.0
FROM apify/actor-node-playwright-chrome:22-1.52.0

In your package.json, match the Playwright/Puppeteer version to your Docker image tag:

{
"dependencies": {
"apify": "^3.5.0",
"@crawlee/playwright": "^3.15.0",
"playwright": "1.52.0"
}
}
Why version matching matters

When the Playwright/Puppeteer version in your package.json differs from what's pre-installed in the Docker image, npm will download and install the version specified in package.json. This can lead to:

  • Slower builds due to downloading browser binaries
  • Potential version incompatibilities
  • Inconsistent behavior between local development and production

Use * as version (alternative approach)

You may encounter older documentation or templates using * as the Playwright/Puppeteer version:

{
"dependencies": {
"playwright": "*"
}
}

The asterisk (*) tells npm to use whatever version is already installed, which prevents re-downloading the library. While this approach still works, pinning specific versions is now preferred because:

  1. Reproducibility - Your builds will behave the same way regardless of when you build them
  2. Predictability - You know exactly which version you're running
  3. Debugging - Version-specific issues are easier to track down

Node.js package managers

All Node.js images ship with npm and have Corepack enabled, so you can use yarn or pnpm as well. Neither is preinstalled: add a packageManager field to your package.json and Corepack downloads and uses the exact version you pin.

{
"packageManager": "pnpm@10.24.0"
}

The images preconfigure the package managers so that:

  • pnpm and yarn install a flat, npm-style node_modules (node-linker=hoisted for pnpm, nodeLinker: node-modules for yarn) instead of a symlinked store or Plug'n'Play, so dependencies resolve without extra loaders.
  • The yarn and pnpm caches (YARN_CACHE_FOLDER, YARN_GLOBAL_FOLDER, PNPM_CONFIG_STORE_DIR, PNPM_CONFIG_CACHE_DIR) and the Corepack cache (COREPACK_HOME) live under /pkg-cache. npm keeps its default ~/.npm cache. Both directories only hold throwaway data, so you can rm -rf /pkg-cache/* ~/.npm at the end of your Dockerfile to reclaim space without touching installed dependencies.
Overriding the linker

The images set the linker through the PNPM_CONFIG_NODE_LINKER and YARN_NODE_LINKER environment variables. Both pnpm and yarn give environment variables precedence over .npmrc or .yarnrc.yml, so a config file alone does not change the linker. To use a different one, override the variable in your Dockerfile:

# https://pnpm.io/settings#nodelinker
ENV PNPM_CONFIG_NODE_LINKER=isolated
# https://yarnpkg.com/configuration/yarnrc#nodeLinker
ENV YARN_NODE_LINKER=pnp

Custom Dockerfile

Apify uses Docker to build and run Actors. If you create an Actor from a template, it already contains an optimized Dockerfile for the given use case.

To use a custom Dockerfile, you can either:

  • Reference it from the dockerfile field in .actor/actor.json,
  • Store it in .actor/Dockerfile or Dockerfile in the root directory (searched in this order of preference).

If no Dockerfile is provided, the system uses the following default:

FROM apify/actor-node:24

COPY --chown=myuser:myuser package*.json ./

RUN npm --quiet set progress=false \
&& npm install --only=prod --no-optional \
&& echo "Installed NPM packages:" \
&& (npm list --only=prod --no-optional --all || true) \
&& echo "Node.js version:" \
&& node --version \
&& echo "NPM version:" \
&& npm --version

COPY --chown=myuser:myuser . ./

For more information about Dockerfile syntax and commands, see the Dockerfile reference.

Custom base images

While apify/actor-node is a base Docker image provided by Apify, you can use other Docker images as the base for your Actors.
However, using the Apify images has some performance advantages, as they are pre-caches on Apify servers.

By default, Apify base Docker images with the Apify SDK and Crawlee start your Node.js application the same way as npm start, i.e, by running the command specified in package.json under scripts - start. The default package.json is similar to:

{
"description": "Anonymous Actor on the Apify platform",
"version": "0.0.1",
"license": "UNLICENSED",
"main": "main.js",
"scripts": {
"start": "node main.js"
},
"dependencies": {
"apify": "^3.0.0",
"crawlee": "^3.0.0"
},
"repository": {}
}

This means the system expects the source code to be in main.js by default. If you want to override this behavior, use a custom package.json and/or Dockerfile.

Optimization tips

You can check out various optimization tips for Dockerfile in the Performance documentation.

Build TypeScript Actors

TypeScript Actors compile to JavaScript at build time through a tsc build step. Installing only production dependencies (npm install --omit=dev, or the older --only=prod) strips the typescript package, so the build fails with tsc: not found. Three fixes:

  1. Multi-stage build (smallest runtime image, most complex Dockerfile). Installs dev dependencies in a builder stage, then copies the compiled output into a slim runtime stage.

    FROM apify/actor-node:24 AS builder
    COPY --chown=myuser:myuser package*.json ./
    RUN npm install --include=dev
    COPY --chown=myuser:myuser . ./
    RUN npm run build

    FROM apify/actor-node:24
    COPY --chown=myuser:myuser package*.json ./
    RUN npm install --omit=dev --omit=optional
    COPY --from=builder --chown=myuser:myuser /usr/src/app/dist ./dist
    CMD ["node", "dist/main.js"]
  2. Drop --omit=dev (simplest Dockerfile, largest runtime image). Installs everything, including linters and type declarations, into the single-stage image.

    FROM apify/actor-node:24
    COPY --chown=myuser:myuser package*.json ./
    RUN npm install
    COPY --chown=myuser:myuser . ./
    RUN npm run build
    CMD ["node", "dist/main.js"]
  3. Move typescript to dependencies. Keeps the single-stage Dockerfile but bloats the runtime image with the TypeScript compiler and its tooling. The runtime never uses these, so the extra size is wasted.

    {
    "dependencies": {
    "apify": "^3.4.0",
    "typescript": "^5.5.0"
    }
    }

Update older Dockerfiles

All Apify base Docker images now use a non-root user to enhance security. This change requires updates to existing Actor Dockerfiles that use the apify/actor-node, apify/actor-python, apify/actor-python-playwright, or apify/actor-python-selenium images. This section provides guidance on resolving common issues that may arise during this migration.

If you encounter an issue that is not listed here, or need more guidance on how to update your Dockerfile, please open an issue in the apify-actor-docker GitHub repository.

Action required

As of August 25, 2025 the base Docker images display a deprecation warning that links you here. This warning will be removed start of February 2026, so you should update your Dockerfiles to ensure forward compatibility.

User and working directory

To improve security, the affected images no longer run as the root user. Instead, they use a dedicated non-root user, myuser, and a consistent working directory at /home/myuser. This configuration is now the standard for all Apify base Docker images.

Common issues

Crawlee templates automatically installing git in Python images

If you've built your Actor using a Crawlee template, you might have the following line in your Dockerfile:

RUN apt update && apt install -yq git && rm -rf /var/lib/apt/lists/*

You can safely remove this line, as the git package is now installed in the base image.

uv package manager fails to install dependencies

If you are using the uv package manager, you might have the following line in your Dockerfile:

ENV UV_PROJECT_ENVIRONMENT="/usr/local"

With the move to a non-root user, this variable will cause uv to throw a permission error. You can safely remove this line, or, if you need it set to a custom path, adjust it to point to a location in the /home/myuser directory.

Copying files with the correct permissions

When using the COPY instruction to copy your files to the container, you should append the --chown=myuser:myuser flag to the command to ensure the myuser user owns the files.

Here are a few common examples:

COPY --chown=myuser:myuser requirements.txt ./

COPY --chown=myuser:myuser . ./
warning

If your Dockerfile contains a RUN instruction similar to the following one, you should remove it:

RUN chown -R myuser:myuser /home/myuser

Instead, add the --chown flag to the COPY instruction:

COPY --chown=myuser:myuser . ./

Running chown across multiple files needlessly slows down the build process. Using the flag on COPY is much more efficient.

An apify user is being added by a template

If your Dockerfile has instructions similar to the following, they were likely added by an older template:

# Create and run as a non-root user.
RUN adduser -h /home/apify -D apify && \
chown -R apify:apify ./
USER apify

You should remove these lines, as the new user is now myuser. Don't forget to update your COPY instructions to use the --chown flag with the myuser user.

COPY --chown=myuser:myuser . ./

Install dependencies that require root access

The root user is still available in the Docker images. If you must run steps that require root access (like installing system packages with apt or apk), you can temporarily switch to the root user.

FROM apify/actor-node:24

# Switch to root temporarily to install dependencies
USER root

RUN apt update \
&& apt install -y <dependencies here>

# Switch back to the non-root user
USER myuser

# ... your other instructions

If your Actor needs to run as root for a specific reason, you can add the USER root instruction after FROM. However, for a majority of Actors, this is not necessary.