Hardened Images
Alongside the standard directus/directus image, Directus publishes a Docker Hardened Image (DHI) variant built for security-conscious production deployments. This page explains what a hardened image is and how to run Directus with it.
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. Instead of shipping a full operating system with shells, package managers, and general-purpose utilities, a hardened image includes only what the application needs to run.
Docker Hardened Images are built on a distroless base, which means they do not include a shell, a package manager, or other tools an attacker could use to move around inside a compromised container. Compared to a standard image, a hardened image typically offers:
- A smaller attack surface - removing shells,
npm,npx, 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 also introduce constraints. Because a hardened image has no shell or package manager, you cannot open an interactive shell inside the container or install extensions at runtime. Any customization, such as including extensions, must be done at build time.
Running npm and npx Commands with a Multi-Stage Build
The hardened image removes npm and npx from the runtime, so anything that depends on them, such as installing an extension from npm or running a one-off directus CLI command, cannot run inside the final container. To work around this, use a multi-stage Dockerfile.
A multi-stage build lets you run those commands in an earlier stage that uses the standard Directus image, which still includes npm, npx, and pnpm. You then copy only the resulting files into a final stage based on the hardened image. The tools you need for the build stay out 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 uses the standard image as a build stage to install an extension, then copies the result into a final stage based on the hardened image:
# Build stage - the standard image includes npm, npx, and pnpm
FROM directus/directus:12.1.1 AS build
USER root
RUN corepack enable
USER node
# Install an extension from npm, or run any other npm/npx command here
RUN pnpm install @directus-labs/spreadsheet-layout
# Final stage - the hardened image, without npm or npx
FROM directus/directus:12.1.1-dhi
# Copy the installed extension into the extensions directory
COPY --from=build /directus/node_modules/@directus-labs/spreadsheet-layout /directus/extensions/spreadsheet-layout
The build stage runs your npm or npx commands, and the COPY --from=build instruction carries the built extension into the extensions directory of the hardened final stage, where Directus loads it on startup. Only the final stage is published as your image, so the build tooling is never part of your runtime.
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 both stages to the same Directus version to keep the build and runtime consistent. 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
On the standard image, you run the Directus CLI through npx directus <command>. The hardened image has no npx, but the CLI itself is still present in the container. 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.