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.com
Later 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.com
ampatspell@gmail.com
Let's Encrypt
Open 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:
Directus
Docker Compose
Open Directus
service:
Yes
Go 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:
Directus
demo-directus.app.amateurinmotion.com
/
/
8055
On
Let's Encrypt
Click 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:
Application
Frontend
Open Frontend app:
demo
main
/
On Push
Build type:
Dockerfile
Dockerfile
.
Deploy.
Open Domains:
demo.app.amateurinmotion.com
/
/
3000
On
Let's Encrypt
Open domain, wait for Let's Encrypt.
Open Directus at https://demo-directus.app.amateurinmotion.com
In Admin > Data Model:
hello
In the newly created data model:
message
In Content > Hello:
To whom it may concern it is springtime it is late afternoon.
Open Users:
Frontend
Frontend
hello
All access
PUBLIC_DIRECTUS_TOKEN
, save it somewhere)Open Admin User:
PRIVATE_ADMIN_TOKEN
, also save it somewhere)Open GitHub
app.amateurinmotion read:packages for npm install
Set it to more than 7 days.
read:packages
Expiration: 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.json
In 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.