All Templates
Directus logo

Directus

Backend

Instant REST and GraphQL API over your own database

Deploy Now

README

Instant REST and GraphQL API over your own database, with an admin app on top.

Overview

Directus wraps a SQL database in a REST and GraphQL API and an admin app, without owning the schema. It introspects whatever tables are there, so the database stays a plain database: you can keep reading and writing it with psql, an ORM or a BI tool while Directus serves the same rows over HTTP. On top of that it adds roles and granular permissions, file storage with on-the-fly image transforms, revisions and an activity log, and Flows, a visual automation builder with webhook, event and schedule triggers.

This template runs upstream's official image unmodified. There is no overlay Dockerfile here and no code in this repository between you and the app: the container's own entrypoint installs the schema, runs migrations, seeds the first administrator and then supervises the server with pm2, exactly as it does anywhere else.

What you get by hosting it

  • A REST and GraphQL API over your data, generated from the schema rather than written by hand.
  • An admin app for modelling collections and fields, and for editing content in them.
  • Users, roles and policies, with per-collection and per-field permissions and row filters.
  • File storage with image transforms, plus revisions and an activity log over every change.
  • Flows: webhook, event, manual and schedule-triggered automations, built in the UI.

What you need before deploying

  • An email address and a password for the first administrator (ADMIN_EMAIL and ADMIN_PASSWORD). The email is the login, so it has to be a real address shape.
  • Nothing else. Collections, users and flows are all created in the app after it starts.

Configuration

VariableRequiredDescription
ADMIN_EMAILyesEmail address for the first administrator, created on first boot. It is the login, and must be a valid address shape; it only has to receive mail if you configure SMTP.
ADMIN_PASSWORDyesPassword for that administrator. Directus enforces no password policy by default. It is not generated here on purpose: a template variable is stored write-only, so a generated password could never be read back.
PROJECT_NAMEnoName shown on the login page and in the admin app's header. Applied on first boot only; afterwards it is Settings > Project Settings.
PROJECT_OWNERnoEmail of the person responsible for this deployment. Directus asks for one in a dialog on first sign-in, to record licence acceptance; setting it here answers that in advance. Applied on first boot only, and ignored unless it parses as an email.
AUTH_PASSWORD_POLICYnoRegex every password must match. Unset means no policy, which is upstream's default.
CORS_ENABLEDnoSet to true to let browsers on other origins call the API. Off by default; the admin app is same-origin.
CORS_ORIGINnoWhich origins CORS allows when CORS_ENABLED is true. An origin, a comma-separated list, or true to echo the request's own origin.
WEBSOCKETS_ENABLEDnoSet to true to serve the realtime API at /websocket. Off by default.
EMAIL_FROMnoAddress outbound mail is sent from. Needed for password resets, user invites and the Send Email flow operation.
EMAIL_TRANSPORTnosendmail (upstream's default, and this image carries no MTA), smtp, mailgun, sendgrid or ses.
EMAIL_SMTP_HOST, EMAIL_SMTP_PORT, EMAIL_SMTP_USER, EMAIL_SMTP_PASSWORDnoThe SMTP server, when EMAIL_TRANSPORT is smtp.

SECRET is generated by the platform and never asked for. It signs access tokens, session cookies and password-reset tokens; Directus warns and falls back to a fresh random value on every boot when it is unset, which would log every user out on every restart.

What the template fixes

The database is SQLite on the service's volume, which is the image's own default client with the file moved: DB_CLIENT=sqlite3 and DB_FILENAME=/data/database.sqlite. The file sits at the volume root rather than in a subdirectory because nothing in Directus creates one: upstream's Dockerfile pre-creates /directus/database at build time, and a fresh volume mounted over a different path would arrive without it. Uploads go to /data/uploads and Marketplace extensions to /data/extensions, both on the same volume.

PUBLIC_URL is set to the service's own URL. It is what the app puts in password-reset and email-verification links, in SSO redirect targets and in the MCP resource URLs.

The service listens on 8055 and is health-checked on /server/ping, which answers 200 pong before the authenticate middleware runs. Neither of the two likelier paths works: / is a 302 to /admin, and /server/health, the app's own readiness probe, throws ForbiddenError for an unauthenticated caller in Directus 12 and answers 403. /server/ping does not touch the database, which it does not need to here: the entrypoint runs directus bootstrap to completion and only hands off to pm2 if it exited 0, so the server binds at all only after the schema is installed and migrated.

The first boot logs Upload directory (/data/uploads) is not read/writeable! and Extensions directory (/data/extensions) is not readable!. Both are a boot-time access() on a directory that does not exist yet rather than a permission problem: the same check passes for the SQLite file's directory, which is the same volume. Each directory is created on first use, and the warning stops from the next boot on.

It is declared alwaysOn: true. Flows with a schedule trigger fire from inside the process, and the app runs its own timed jobs besides, so a machine that had scaled to zero would never wake to run one. That bills continuously.

Licensing, and what the unlicensed tier allows

Directus 12 is source-available under the Monospace Sustainable Core License 1.0, not an OSS licence, and it meters itself. With no licence key entered, the built-in Core entitlements (read off @directus/license 0.6.1, which the image ships) are:

EntitlementCore
Users (seats)3
Collections25
Flows5
Activity and revision history30 days
SSOoff
Telemetryrequired, and TELEMETRY=false is ignored while it is

A licence key is entered in the app after deploy, not through a template variable. Read the licence in the upstream repository before using this commercially: it grants internal use, non-commercial education and research, and hosting on a licensee's behalf, but not a Competing Use.

After deploy

  1. Open the service URL and sign in with the ADMIN_EMAIL and ADMIN_PASSWORD you set. The first sign-in raises two dialogs before you reach anything: one asking for a licence key, where I'm using Core plan or Skip is the free path, and one asking for a project owner email and licence acceptance, which has a Remind Later. Setting PROJECT_OWNER answers the second in advance.
  2. Settings > Data Model > Create Collection to make your first table. Add fields to it, then switch to the Content module to add rows.
  3. The same rows are already an API. GET <service-url>/items/<collection> serves them, and POST <service-url>/graphql serves the GraphQL equivalent. Both need a token.
  4. To get a token: User Directory > your user > Token, generate one, and send it as Authorization: Bearer <token>. Or exchange your login for one at POST /auth/login with {"email":..., "password":...}.
  5. Settings > Access Policies is where you open a collection up to the Public role, if you want unauthenticated reads.

Links

  • Architectures: linux/amd64 and linux/arm64. Upstream's 12.5.0 index carries both; nothing is rebuilt here.
  • Upstream: https://github.com/directus/directus
  • Documentation: https://directus.com/docs/
  • Image: docker.io/directus/directus, pinned to 12.5.0
  • License: Monospace Sustainable Core License 1.0 (license in the upstream repository root, self-abbreviated MSCL-1.0-GPL). Source-available, not OSS.

Services & Specs

directus
Web service
Image
docker.io/directus/directus:12.5.0
Port
8055
Healthcheck
/server/ping

Variables

You supply 2 variables before the first deploy.

Required

ADMIN_EMAIL

Email address for the first administrator, created on first boot. It is the login, so it must be a real address shape; it does not have to receive mail unless you configure SMTP

ADMIN_PASSWORD

Password for that administrator. Directus enforces no policy by default, so this is the only thing between the public URL and your data: pick a strong one

Optional (12)
PROJECT_NAME

Name shown on the login page and in the admin app's header. Applied on first boot only; afterwards it is Settings > Project Settings in the app

PROJECT_OWNER

Email of the person responsible for this deployment. Directus 12 asks for one in a dialog on first sign-in, to record licence acceptance; setting it here answers that in advance. Applied on first boot only, and ignored unless it parses as an email

AUTH_PASSWORD_POLICY

Regex every password must match, e.g. /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/. Unset means no policy, which is upstream's default

CORS_ENABLED

Set to true to let browsers on other origins call the API. Off by default; the admin app is same-origin and does not need it

CORS_ORIGIN

Which origins CORS allows when CORS_ENABLED is true. An origin, a comma-separated list, or true to echo the request's own origin

WEBSOCKETS_ENABLED

Set to true to serve the realtime API at /websocket, for subscribing to item changes. Off by default

EMAIL_FROM

Address outbound mail is sent from. Needed for password resets, user invites and the Send Email flow operation

EMAIL_TRANSPORT

How mail is sent: sendmail (the default, which has no MTA in this image), smtp, mailgun, sendgrid or ses. Set smtp and fill the four EMAIL_SMTP_ variables below

EMAIL_SMTP_HOST

SMTP server hostname, when EMAIL_TRANSPORT is smtp

EMAIL_SMTP_PORT

SMTP server port, when EMAIL_TRANSPORT is smtp. Commonly 587

EMAIL_SMTP_USER

SMTP username, when EMAIL_TRANSPORT is smtp

EMAIL_SMTP_PASSWORD

SMTP password, when EMAIL_TRANSPORT is smtp