Deploy Cockpit | (Just Updated) Headless CMS With REST And GraphQL API, Admin And API Key Generated
Cockpit headless CMS with REST and GraphQL API, SQLite on a volume
Just deployed
/var/www/html/storage
Deploy and Host Cockpit on Railway
Built for an LLM agent to deploy and use. The deploy form has three generated fields and nothing to fill in: the administrator password, the API key and the signing key are generated, the administrator account and its API key are created at first boot, and content, users and uploads survive redeploys. Cockpit is an open-source, self-hosted headless CMS: you define content models (collections, singletons, trees), edit content in an admin UI, and read and write it through a REST and GraphQL API.
About Hosting Cockpit
This template runs one service, cockpit, on one public domain with a volume at /var/www/html/storage. It is the upstream Cockpit 2.14.1 image pinned by digest, with SQLite storage, so the user table, the content models, the content items and the uploaded assets all sit on the volume.
No install form and no default login. The stock image opens a /install page where the first visitor becomes the administrator, and ships a constant signing key. This wrapper creates the administrator admin at boot from the generated COCKPIT_ADMIN_PASSWORD (32 characters), stores the generated COCKPIT_API_KEY (40 characters) as that account's API key, and takes the signing key from the generated COCKPIT_SEC_KEY (48 characters), so no two deploys share a key. The password is set only when the account is created, so one changed in the admin UI survives a redeploy; the API key follows the variable, so rotating it and redeploying changes the key. The healthcheck is /api/system/healthcheck, which answers 200 once the server is up.
What the wrapper fixes for Railway. Railway mounts a volume owned by root and the image runs unprivileged, so the entrypoint creates the storage folders before the server starts. The upstream web server config already answers 403 for any .sqlite file. The entrypoint also points the web server at Railway's injected PORT.
Using it. Sign in at https://YOUR_DOMAIN/ as admin with COCKPIT_ADMIN_PASSWORD, create a model under Content, and add items. The API reads items from /api/content/items/{model} and /api/content/item/{model}, and GraphQL answers at /api/gql. Send the API key in the api-key header, prefixed with USR-.
Agent contract
- Public URL:
https://RAILWAY_PUBLIC_DOMAIN(the generated domain of thecockpitservice) - Private URL:
http://cockpit.railway.internal:8080 - Credentials: the variables
COCKPIT_ADMIN_PASSWORD(the password of the fixed useradmin, not a template variable) andCOCKPIT_API_KEYon thecockpitservice - Auth: the REST and GraphQL API take the header
api-keyset toUSR-followed by the value ofCOCKPIT_API_KEY. A request with no key or a wrong key answers412, and a bearer token that is not a key answers401. The admin UI takes a session cookie fromPOST /auth/checkwith a CSRF token read from the login page. - Health:
GET /api/system/healthcheckreturns 200
#!/bin/sh
# needs: RAILWAY_PUBLIC_DOMAIN, COCKPIT_ADMIN_PASSWORD, COCKPIT_API_KEY (variables of the cockpit service); also curl.
R="https://$RAILWAY_PUBLIC_DOMAIN"; J="Content-Type: application/json"; K="api-key: USR-$COCKPIT_API_KEY"
n=0; until [ "$(curl -s -o /dev/null -w '%{http_code}' "$R/api/system/healthcheck")" = 200 ]; do n=$((n+1)); [ "$n" -le 60 ] || break; sleep 5; done
curl -s -o /dev/null -w "health: %{http_code}\n" "$R/api/system/healthcheck"
# one-time setup: sign in as admin and create a "notes" collection (the CMS stores no models until one exists)
csrf() { grep -o "csrf$1\"[^\"]*\"" | head -1 | cut -d'"' -f2; }
T=$(curl -s -c jar.txt "$R/auth/login" | csrf ': ')
curl -s -b jar.txt -c jar.txt -o /dev/null -w "admin login: %{http_code}\n" -X POST "$R/auth/check" -H "$J" -d "{\"auth\":{\"user\":\"admin\",\"password\":\"$COCKPIT_ADMIN_PASSWORD\"},\"csrf\":\"$T\"}"
T=$(curl -s -b jar.txt "$R/" | csrf '=')
curl -s -b jar.txt -o /dev/null -w "create model: %{http_code}\n" -X POST "$R/content/models/save" -H "$J" -H "X-Csrf-Token: $T" -d '{"model":{"name":"notes","type":"collection","fields":[{"name":"title","type":"text","label":"Title"}]}}'
# the REST API, authenticated with the API key
curl -s -o /dev/null -w "write with key: %{http_code}\n" -X POST "$R/api/content/item/notes" -H "$K" -H "$J" -d '{"data":{"title":"agent note"}}'
curl -s -H "$K" "$R/api/content/items/notes" | grep -c '"agent note"' | sed 's/^/note found after write: /'
curl -s -o /dev/null -w "read with key: %{http_code}\n" -H "$K" "$R/api/content/items/notes"
curl -s -o /dev/null -w "read without key: %{http_code}\n" "$R/api/content/items/notes"
curl -s -o /dev/null -w "write without key: %{http_code}\n" -X POST "$R/api/content/item/notes" -H "$J" -d '{"data":{"title":"anonymous"}}'
curl -s -o /dev/null -w "write with forged token: %{http_code}\n" -X POST "$R/api/content/item/notes" -H "Authorization: Bearer a.b.c" -H "$J" -d '{"data":{"title":"forged"}}'
curl -s -o /dev/null -w "write with wrong key: %{http_code}\n" -X POST "$R/api/content/item/notes" -H "api-key: USR-wrong" -H "$J" -d '{"data":{"title":"wrong"}}'
A fresh deploy prints health: 200, admin login: 200, create model: 200, write with key: 200, note found after write: 1 and read with key: 200. The refusals print read without key: 412, write without key: 412, write with wrong key: 412 and write with forged token: 401: Cockpit answers a request with no key or a wrong key with 412 "Authentication failed", and only the forged bearer token reaches a 401. In every case nothing is written.
Why Deploy Cockpit on Railway?
Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.
By deploying Cockpit on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.
- Agent-ready - three generated variables, a contract block and one script that proves health, sign-in, model creation, an authenticated write and read-back, and the refusal of a write with no key, a wrong key and a forged token.
- No install race and no shared key - the administrator and API key are created at boot from generated values, and every deploy signs with its own key.
- Data survives redeploys - users, models, content and uploads sit in SQLite on the volume.
- Rotatable API key - change
COCKPIT_API_KEYand redeploy; the account's key follows it. - Pinned - the upstream image is pinned by digest and the wrapper by tag and digest.
Common Use Cases
- Headless CMS for a website or app - model pages, posts and settings once, then fetch them as JSON from a static site, a mobile app or a server.
- Content store for AI agents - give an agent an API key to save notes, drafts and structured results and read them back.
- GraphQL content backend - query related content in one request from a front end.
- Self-hosted alternative to a hosted CMS - keep content and uploads on your own volume instead of a per-seat service.
Dependencies for Cockpit Hosting
- The cockpit service with its volume, created by the template. No separate database is needed: Cockpit stores its data in SQLite on the volume.
Deployment Dependencies
Template Content