This guide describes the steps necessary to get Directus and SvelteKit app deployed to your own server.
I'm describing steps based on app.amateurinmotion.com domain and Demo application. Replace those with your domain and application name.
References:
See https://docs.dokploy.com/docs/core/installation for a referrial link for Hetzner to get 20€ off.
Add A records pointing to your server's IP:
app.amateurinmotion.com*.app.amateurinmotion.comLater we'll need random strings. On your local machine add the following alias to your ~/.zshrc:
alias rand="openssl rand -base64"
Open new Terminal (or source ~/.zshrc) to test it out:
air:~ $ rand 32
T/kCF2Q7FIZLcZc8UE/Sc0soh+U5qsaLDVpTCpfAli4=
As root on your server run Dokploy installer:
curl -sSL https://dokploy.com/install.sh | sh
This will install docker, docker compose, dokplay. When it's done, open a http://<ip>:3000 URL provided by the installer.
Sign up.
In Dokploy open:
app.amateurinmotion.comampatspell@gmail.comLet's EncryptOpen https://app.amateurinmotion.com, sign in with the same credentials. Maybe change your password – previous one was set in non-SSL connection.
Open:
Then open:
Open:
Open:
Click on:
DirectusDocker ComposeOpen Directus service:
YesGo Back to General:
Raw providerPaste the following docker-compose.yaml and Save:
services:
directus:
image: 'directus/directus:11'
volumes:
- 'database:/directus/database'
- 'uploads:/directus/uploads'
environment:
- PUBLIC_URL=${PUBLIC_URL}
- KEY=${KEY}
- SECRET=${SECRET}
- ADMIN_EMAIL=${ADMIN_EMAIL}
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
- DB_CLIENT=sqlite3
- DB_FILENAME=/directus/database/data.db
- WEBSOCKETS_ENABLED=true
- ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION=10000
- MARKETPLACE_TRUST=${MARKETPLACE_TRUST}
- CORS_ENABLED=true
- CORS_METHODS=GET,POST,PATCH,DELETE,OPTIONS
- CORS_ORIGIN=${CORS_ORIGIN}
restart: always
healthcheck:
test:
- CMD
- wget
- '-q'
- '--spider'
- 'http://127.0.0.1:8055/admin/login'
interval: 5s
timeout: 20s
retries: 10
volumes:
uploads:
database:
Open Environment, paste the following:
PUBLIC_URL=https://demo-directus.app.amateurinmotion.com
CORS_ORIGIN=https://demo.app.amateurinmotion.com,http://localhost:5173
KEY=<KEY>
SECRET=<SECRET>
ADMIN_EMAIL=ampatspell@gmail.com
ADMIN_PASSWORD=<PASSWORD>
MARKETPLACE_TRUST=sandbox
MARKETPLACE_TRUST can be either sandbox or all.
Save.
Generate three random strings (with rand command) and replace <KEY>, <SECRET> and <PASSWORD> with them.
Save.
Go back to General and click Deploy. Wait for the green light.
Go to Domains and add new:
Directusdemo-directus.app.amateurinmotion.com//8055OnLet's EncryptClick on the link, wait for Let's Encrypt.
Sign in with ADMIN_EMAIL and ADMIN_PASSWORD from environment variables above.
nvm use 24
npx sv@latest create
Add a Dockerfile
FROM node:24-alpine AS builder
WORKDIR /app
RUN apk update
RUN apk add --no-cache coreutils
ARG GITHUB_TOKEN
COPY package*.json .
COPY .npmrc .
RUN npm ci
COPY . .
ARG PRIVATE_DIRECTUS_ADMIN_TOKEN
ARG PUBLIC_DIRECTUS_URL
ARG PUBLIC_DIRECTUS_TOKEN
# RUN npm exec tools generate
RUN npm run build
RUN npm prune --production
FROM node:24-alpine
WORKDIR /app
RUN apk --no-cache add curl
COPY --from=builder /app/build build/
COPY --from=builder /app/node_modules node_modules/
COPY package.json .
EXPOSE 3000
ARG PRIVATE_DIRECTUS_ADMIN_TOKEN
ARG PUBLIC_DIRECTUS_URL
ARG PUBLIC_DIRECTUS_TOKEN
ENV NODE_ENV=production
CMD [ "node", "build" ]
HEALTHCHECK \
--interval=1m \
--timeout=10s \
--start-period=5s \
--retries=10 \
CMD curl -f http://localhost:3000 || exit 1
Add .dockerignore
Dockerfile
.dockerignore
.git
.gitignore
.gitattributes
README.md
.prettierrc
.eslintrc.cjs
.graphqlrc
.editorconfig
.svelte-kit
.vscode
node_modules
build
package
**/.env
Publish to GitHub as https://github.com/ampatspell/demo
Open Demo project in Dokploy:
ApplicationFrontendOpen Frontend app:
demomain/On PushBuild type:
DockerfileDockerfile.Deploy.
Open Domains:
demo.app.amateurinmotion.com//3000OnLet's EncryptOpen domain, wait for Let's Encrypt.
Open Directus at https://demo-directus.app.amateurinmotion.com
In Admin > Data Model:
helloIn the newly created data model:
messageIn Content > Hello:
To whom it may concern it is springtime it is late afternoon.Open Users:
FrontendFrontendhelloAll accessPUBLIC_DIRECTUS_TOKEN, save it somewhere)Open Admin User:
PRIVATE_ADMIN_TOKEN, also save it somewhere)Open GitHub
app.amateurinmotion read:packages for npm installSet it to more than 7 days.read:packagesExpiration: This token will be used every time you re-deploy your app.
Open ~/.zshrc and export your token:
export GITHUB_TOKEN="ghp_*****"
In SvelteKit demo app:
Add a .npmrc
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
@ampatspell:registry=https://npm.pkg.github.com
engine-strict=true
Add .env
PUBLIC_DIRECTUS_URL=https://demo-directus.app.amateurinmotion.com
PUBLIC_DIRECTUS_TOKEN=<token from above>
PRIVATE_DIRECTUS_ADMIN_TOKEN=<token from above>
and
npm install \
@ampatspell/base \
@ampatspell/directus
Add generate and tools scripts to package.json:
"scripts": {
…
+ "tools": "tools",
+ "generate": "tools generate"
},
Generate types:
npm run generate
Add src/lib/directys/directus.ts file:
import { type Schema } from './schema';
import { getBaseDirectus, type Fetch } from '@ampatspell/directus/directus';
export const getDirectus = (fetch: Fetch) => getBaseDirectus<Schema>(fetch);
export type Directus = ReturnType<typeof getDirectus>;
Add the following files to .gitignore:
/src/lib/directus/schema.ts/src/lib/directus/diff.jsonIn src/routes add:
// +layout.server.ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async (event) => {
const visualEditingEnabled = event.url.searchParams.get('visual-editing') === 'true';
return {
visualEditingEnabled,
};
};
// +page.server.ts
import { getDirectus } from '$lib/directus/directus';
import { CollectionNames } from '$lib/directus/schema';
import { readSingleton } from '@directus/sdk';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ fetch }) => {
const directus = getDirectus(fetch);
return {
hello: await directus.request(readSingleton(CollectionNames.hello, {}))
};
};
// +page.svelte
<script lang="ts">
let { data } = $props();
let hello = $derived(data.hello);
</script>
<div class="hello">{hello.message}</div>
Uncomment npm run generate in Dockerfile builder layer:
FROM node:24-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
ARG PRIVATE_DIRECTUS_ADMIN_TOKEN
ARG PUBLIC_DIRECTUS_URL
ARG PUBLIC_DIRECTUS_TOKEN
- # RUN npm exec tools generate
+ RUN npm exec tools generate
RUN npm run build
RUN npm prune --production
…
Open Dokploy:
Demo projectFrontend application.env variables to Environment Settings and Build-time VariablesGITHUB_TOKEN to Build-time variablesEnvironment settings:
PUBLIC_DIRECTUS_URL=https://demo-directus.app.amateurinmotion.com
PUBLIC_DIRECTUS_TOKEN=<token from above>
PRIVATE_DIRECTUS_ADMIN_TOKEN=<token from above>
Build-time Variables:
PUBLIC_DIRECTUS_URL=https://demo-directus.app.amateurinmotion.com
PUBLIC_DIRECTUS_TOKEN=<token from above>
PRIVATE_DIRECTUS_ADMIN_TOKEN=<token from above>
GITHUB_TOKEN=<token from above>
Back to the app locally, git commit, push.
Wait a bit.
Open https://demo.app.amateurinmotion.com/
Done.