Hardened Images
Directus publishes Docker images hardened for production: the standard directus/directus image and a distroless Docker Hardened Image (DHI) variant tagged with a -dhi suffix. This page explains what they do and how to run Directus with them.
What is a Hardened Image?
A hardened image is a container image that has been stripped down and locked down to reduce its attack surface. It includes only what the application needs to run, so there are fewer components an attacker could use to move around inside a compromised container.
Directus hardens its images at two levels:
- The standard
directus/directusimage applies OS-level patches at build time and removesnpmandnpxfrom the runtime. It still includes a shell, so you can open an interactive shell inside the container. - The
-dhivariant is a Docker Hardened Image built on a distroless base. On top of the standard image's hardening, it removes the shell, package manager, and other general-purpose utilities entirely.
These images offer:
- A smaller attack surface - removing
npm,npx, shells, and other binaries means there are fewer components to exploit. - Fewer vulnerabilities - a minimal set of packages results in fewer reported CVEs to track and patch.
- A non-root runtime - the container runs as an unprivileged user by default.
- A smaller image size - fewer packages means faster pulls and less to store.
These same properties introduce constraints. Because neither image ships npm or npx, you cannot install extensions at runtime on either one. The -dhi variant has no shell at all, so you also cannot open an interactive shell inside it. Any customization, such as including extensions, must be done at build time.
Running npm and npx Commands with a Multi-Stage Build
Since neither image ships npm or npx, use a multi-stage Dockerfile to install extensions or run other build-time commands. Run those commands in an earlier stage based on a node:*-alpine image, then copy only the resulting files into a final stage based on the hardened image. The build tooling never becomes part of the image you ship.
This is the recommended approach when you need to:
- Install extensions from npm.
- Run
directusCLI commands, such asdirectus bootstrapor database migrations, as part of your build. - Run any other tooling that relies on
npmornpx.
Example Dockerfile
The following Dockerfile installs an extension in a node:*-alpine build stage, then copies it into a hardened final stage:
# Build stage
FROM node:{version}-alpine AS build
RUN corepack enable
WORKDIR /extension-build
RUN pnpm init
RUN pnpm add @directus-labs/spreadsheet-layout
# Final stage - the hardened image
FROM directus/directus:{version}-dhi
COPY --from=build /extension-build/node_modules/@directus-labs/spreadsheet-layout /directus/extensions/spreadsheet-layout
node:*-alpine build stage in sync with the version the runtime image is built on. An extension with native dependencies compiled against a different version can fail to load at runtime. Check the Directus Dockerfile for the version currently in use.The COPY --from=build instruction carries the built extension into the extensions directory, where Directus loads it on startup. Only the final stage is published, so the build tooling never ends up in your runtime.
This example ships the distroless -dhi image. Drop the -dhi suffix to build on the standard image instead, which is still hardened but not distroless - the process is identical.
Add a COPY line for each extension you install. Each copied extension should be a directory containing a package.json file and a dist directory, matching the structure Directus expects in the extensions directory.
Pin the final stage to the Directus version you want to run. Build the image with docker compose build and start it with docker compose up as normal, following the same steps described in Including Extensions.
Running the Directus CLI
Normally you run the Directus CLI through npx directus <command>, but neither image ships npx. The CLI itself is still present in the container, so you can call it directly with node, pointing at the CLI entrypoint at /directus/cli.js:
docker compose exec directus node /directus/cli.js <command>
This runs against a container that is already up, so use it for one-off tasks against a running deployment. For example, to apply the latest database migrations:
docker compose exec directus node /directus/cli.js database migrate:latest
Any command you would normally pass to the Directus CLI works the same way:
# Bootstrap the project (run migrations and create the initial admin user)
docker compose exec directus node /directus/cli.js bootstrap
# Roll the database back one migration
docker compose exec directus node /directus/cli.js database migrate:down
# Reset a user's password
docker compose exec directus node /directus/cli.js users passwd --email admin@example.com --password new-password
Get once-a-month release notes & real‑world code tips...no fluff. 🐰
Including Extensions
Learn how to include extensions in your self-hosted Directus project.
Tutorials
This is a collection of step-by-step guides and practical examples to help you get the most out of our platform. Whether you're a beginner or an experienced user, our tutorials are designed to provide you with the knowledge and skills you need to succeed.