KeystoneJS is a powerful headless CMS and web application framework that generates a GraphQL API and admin UI from your schema definition. Built on Prisma and GraphQL, it provides type-safe database access and a beautiful admin interface. This guide covers deploying KeystoneJS on a VPS.
Project Setup
# Create KeystoneJS project
npm init keystone-app@latest my-keystone
cd my-keystone
# Select starter project or blank
Schema Definition
// keystone.ts
import { config, list } from "@keystone-6/core";
import { allowAll } from "@keystone-6/core/access";
import { text, relationship, password, timestamp, select, integer, checkbox } from "@keystone-6/core/fields";
import { document } from "@keystone-6/fields-document";
const lists = {
User: list({
access: allowAll,
fields: {
name: text({ validation: { isRequired: true } }),
email: text({ isIndexed: "unique", validation: { isRequired: true } }),
password: password({ validation: { isRequired: true } }),
posts: relationship({ ref: "Post.author", many: true }),
},
}),
Post: list({
access: allowAll,
fields: {
title: text({ validation: { isRequired: true } }),
slug: text({ isIndexed: "unique" }),
status: select({
options: [
{ label: "Draft", value: "draft" },
{ label: "Published", value: "published" },
],
defaultValue: "draft",
ui: { displayMode: "segmented-control" },
}),
content: document({
formatting: true,
dividers: true,
links: true,
layouts: [
[1, 1], [1, 1, 1],
],
}),
author: relationship({ ref: "User.posts" }),
publishDate: timestamp(),
},
}),
Tag: list({
access: allowAll,
fields: {
name: text(),
posts: relationship({ ref: "Post", many: true }),
},
}),
};
export default config({
db: {
provider: "postgresql",
url: process.env.DATABASE_URL || "postgres://localhost/keystone",
},
lists,
server: {
port: parseInt(process.env.PORT || "3000"),
cors: {
origin: [process.env.FRONTEND_URL || "http://localhost:7777"],
},
},
});
Building and Running
# Development
npm run dev
# Build for production
npm run build
# Start production
npm start
# Push schema to database
npm run build -- --db-push
GraphQL API
# Auto-generated GraphQL at /api/graphql
# List posts
query {
posts(where: { status: { equals: "published" } }, orderBy: { publishDate: desc }) {
id
title
slug
content { document }
author { name }
}
}
# Create post
mutation {
createPost(data: {
title: "My First Post"
slug: "my-first-post"
status: "published"
content: { document: [...] }
}) {
id
title
}
}
Docker Deployment
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.keystone ./.keystone
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/schema.prisma ./
COPY package*.json keystone.ts ./
USER node
EXPOSE 3000
CMD ["npm", "start"]
Systemd Service
[Unit]
Description=KeystoneJS Application
After=network.target postgresql.service
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/keystone
ExecStart=/usr/bin/npm start
EnvironmentFile=/opt/keystone/.env
Environment=NODE_ENV=production
Restart=always
[Install]
WantedBy=multi-user.target
Summary
KeystoneJS provides an elegant code-to-API experience: define your schema in TypeScript, and get a GraphQL API, admin UI, and Prisma database layer automatically. The document field provides rich content editing, relationships handle complex data models, and access control secures everything. Self-hosting on a VPS gives you a powerful, customizable CMS and API backend with full data ownership.