---
title: "Deploy Cockpit | (Just Updated) Headless CMS With REST And GraphQL API, Admin And API Key Generated"
description: "Cockpit headless CMS with REST and GraphQL API, SQLite on a volume"
category: "CMS"
url: https://railway.com/deploy/cockpit-or-just-updated-headless-cms-wit
---

# 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

**[Deploy Cockpit | (Just Updated) Headless CMS With REST And GraphQL API, Admin And API Key Generated on Railway](https://railway.com/template/cockpit-or-just-updated-headless-cms-wit)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/cockpit-or-just-updated-headless-cms-wit/manifest.json

- **Creator:** SuperSlowSloth
- **Category:** CMS

## Template content

### cockpit

- **Image:** ghcr.io/bon5co/cockpit-railway:2.14.1@sha256:6c2b3ceee997b6e4b8c21087546e572ecf0b17f87fe83c92ca0078d3282f328a
- **Health check:** /api/system/healthcheck
- **Public domain:** Yes

## Documentation

# 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 the `cockpit` service)
- Private URL: `http://cockpit.railway.internal:8080`
- Credentials: the variables `COCKPIT_ADMIN_PASSWORD` (the password of the fixed user `admin`, not a template variable) and `COCKPIT_API_KEY` on the `cockpit` service
- Auth: the REST and GraphQL API take the header `api-key` set to `USR-` followed by the value of `COCKPIT_API_KEY`. A request with no key or a wrong key answers `412`, and a bearer token that is not a key answers `401`. The admin UI takes a session cookie from `POST /auth/check` with a CSRF token read from the login page.
- Health: `GET /api/system/healthcheck` returns 200

```bash
#!/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_KEY` and 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

- [Cockpit repository](https://github.com/Cockpit-HQ/Cockpit)
- [Wrapper image source](https://github.com/bon5co/cockpit-railway)


## Similar templates

- [Libredesk - Complete Setup](https://railway.com/deploy/libredesk-complete-setup) — Complete self-hosted omnichannel customer support desk.
- [Paperless-ngx](https://railway.com/deploy/paperless-ngx-3) — Paperless-ngx — document management with OCR and full-text search
- [Instatic CMS - Postgres](https://railway.com/deploy/instatic-cms-postgres) — Design, build and manage powerful static sites from state-of-the-art CMS

Open this page in a browser: https://railway.com/deploy/cockpit-or-just-updated-headless-cms-wit
