Dashtro
A self-hosted CMS with a project → workspace → collection → document model, a
FastAPI backend, and a React/TypeScript frontend. Ships as a single Docker
image, published to ghcr.io/1atharvad/dashtro. This repo only builds and
publishes that image — running it in production (nginx, tunnel/domain
routing, etc.) is owned by the consuming project (e.g. the portfolio site
that embeds Dashtro as its admin/CMS backend).
Contents
- Structure
- Packages
- Architecture
- Data backends
- Running locally (dev)
- Running the image elsewhere
- Environment variables
- Backup / restore CLI
- CI/CD
- Tests
Structure
| Path | What it is |
|---|---|
cms_backend/ |
FastAPI backend — API, auth, schema engine, data clients. See its README. |
cms-frontend/ |
React + TypeScript + Vite frontend. See its README. |
cms_mcp/ |
MCP server exposing Dashtro operations to MCP-compatible clients over /api/sdk/* (Python, console script dashtro-mcp, API-key auth). |
sdk/js/ |
@dashtro/client — JS/TS client SDK for /api/sdk/*. |
sdk/python/ |
dashtro-client — Python client SDK for /api/sdk/*, released in lockstep with sdk/js/. |
sdk/mcp/ |
@dashtro/mcp — npx-runnable MCP server (Node/TS port of cms_mcp/) for consuming projects; talks to /api/sdk/* over HTTP with an API key, no Python required. |
nginx/ |
Reverse proxy config for local dev only. |
Packages
Everything this repo publishes, and what it's for:
| Package | Registry | Install | What it's for |
|---|---|---|---|
ghcr.io/1atharvad/dashtro |
GHCR | docker pull ghcr.io/1atharvad/dashtro |
The CMS itself — backend + built frontend in one image. See Running the image elsewhere. |
@dashtro/client |
npm | npm install @dashtro/client |
JS/TS client SDK for /api/sdk/* — read/write a project's documents and RTDB from an external app. |
dashtro-client |
PyPI | pip install dashtro-client |
Python equivalent of @dashtro/client, released in lockstep with it. |
@dashtro/mcp |
npm | npx @dashtro/mcp init |
npx-runnable MCP server — lets Claude/other MCP clients read and write your CMS content. Node port of cms_mcp/, no Python needed. |
dashtro |
PyPI | pip install dashtro |
The dashtro backup/restore CLI (cms_backend/scripts/cms_schema.py, see Backup / restore CLI). Also installs dashtro-mcp (direct-database-access MCP server) — use @dashtro/mcp instead unless you specifically need that. |
Architecture
flowchart LR
Browser["Browser SPA\n(cms-frontend)"] -->|"/api/*"| Backend["FastAPI backend\n(cms_backend)"]
Backend --> DB[("sqlite / postgres")]
ExternalApp["External app"] -->|"/api/sdk/*"| Backend
ExternalApp -.->|uses| ClientSDK["@dashtro/client\ndashtro-client"]
MCPClient["MCP client\n(Claude, etc.)"] -->|stdio, npx| MCPNode["@dashtro/mcp\n(Node)"]
MCPNode -->|"/api/sdk/*"| Backend
MCPClient -.->|stdio, local install| MCPPy["dashtro-mcp\n(cms_mcp, Python)"]
MCPPy -->|"/api/sdk/*"| Backend
Everything ultimately talks to the same FastAPI backend — the frontend over
its own JWT-authenticated API, and everything else (external apps via either
client SDK, and MCP clients via either MCP server) over the API-key-scoped
/api/sdk/* surface only. MCP clients never see a user's JWT — what an MCP
client can do is exactly what its configured API key is scoped to
(project/collections, read vs. write). Pick @dashtro/mcp for a zero-install
npx setup, or cms_mcp/dashtro-mcp if you're already in a Python
environment.
Data backends
DB_TYPE selects the storage backend, defaulting to sqlite:
sqlite— single-file DB, zero external dependencies. Default.postgres— setDB_HOST/DB_PORT/DB_NAME/DB_USER/DB_PASSWORD. An optional bundledpostgrescompose service is available (see below) if you don't want to point at an external instance.
Both backends implement the same interface (get_data_client() / get_auth_client() /
get_audit_client() in cms_backend/api/utils/__init__.py), so routers don't change
depending on which one is active.
Running locally (dev)
Hot-reloading, separate frontend/backend containers, Cloudflare tunnel support:
cp .env.example .env # fill in real values
npm run dev # docker compose -f docker-compose.dev.yml up --build
npm run dev:down
Optional local Postgres instead of an external one:
docker compose -f docker-compose.dev.yml --profile postgres up
Running the image elsewhere
The published image serves both the API and the built SPA on port 7312, and
reads its config from env vars (see below) — no other dependency beyond
whichever DB_TYPE backend you point it at:
dashtro:
image: ghcr.io/1atharvad/dashtro:latest
environment:
JWT_SECRET_KEY: ...
CORS_ORIGINS: ...
CMS_PUBLIC_URL: ...
DB_TYPE: sqlite
# ...see .env.example for the full list
volumes:
- uploads_data:/app/uploads
Put it behind whatever reverse proxy/tunnel the consuming project already
uses to route a subdomain (e.g. admin.example.com) to it.
Environment variables
See .env.example for the full list (DB_TYPE, JWT_SECRET_KEY,
CORS_ORIGINS, CMS_PUBLIC_URL, etc.).
Backup / restore CLI
cms_backend/scripts/cms_schema.py (installed as the dashtro console script)
exports/imports schemas, documents, and media to/from a backup/ directory.
Local (direct database access)
# Export
dashtro export schema --project-id <id>
dashtro export documents --project-id <id> --workspace <name>
dashtro export media
# Import
dashtro import schema --project-id <id>
dashtro import documents --project-id <id> --workspace <name>
dashtro import media
Run against a running container:
docker exec <container> dashtro export schema --project-id <id> --backup-dir /app/backup
docker exec <container> dashtro import schema --project-id <id> --backup-dir /app/backup
Remote (HTTP API with authentication)
Use --base-url to export/import from/to a remote Dashtro instance. Requires an API key generated in the CMS settings.
Via command line:
# Export from remote
dashtro export schema --project-id <id> --base-url https://your-cms.com --api-key <api-key>
dashtro export documents --project-id <id> --workspace <name> --base-url https://your-cms.com --api-key <api-key>
dashtro export media --base-url https://your-cms.com --api-key <api-key>
# Import to remote
dashtro import schema --project-id <id> --base-url https://your-cms.com --api-key <api-key>
dashtro import documents --project-id <id> --workspace <name> --base-url https://your-cms.com --api-key <api-key>
dashtro import media --base-url https://your-cms.com --api-key <api-key>
Via environment variable (recommended):
export CMS_API_KEY=<api-key>
dashtro export schema --project-id <id> --base-url https://your-cms.com
dashtro import documents --project-id <id> --workspace <name> --base-url https://your-cms.com
Full backup/restore workflow
Export must run in order: schema → documents → media. Restore uses the same order:
# Backup from source instance
dashtro export schema --project-id abc123 --base-url https://source.com --api-key <key>
dashtro export documents --project-id abc123 --workspace production --base-url https://source.com --api-key <key>
dashtro export media --base-url https://source.com --api-key <key>
# Restore to destination instance
dashtro import schema --project-id abc123 --base-url https://dest.com --api-key <key>
dashtro import documents --project-id abc123 --workspace production --base-url https://dest.com --api-key <key>
dashtro import media --base-url https://dest.com --api-key <key>
Options:
| Flag | Meaning |
|---|---|
--backup-dir |
Backup directory location (default: ./backup/) |
--base-url |
Remote API URL (if omitted, uses direct database access) |
--api-key |
API key for authentication (or set CMS_API_KEY env var) |
--merge |
Merge documents instead of replacing (local mode only) |
CI/CD
.github/workflows/build-image.yml runs
on every push to main (or manually via workflow_dispatch):
lint— frontendnpm run lint+ backendisort/black/ruff --check. Must pass before anything builds.build-and-push— buildsDockerfile.dashtro, pushes toghcr.io/1atharvad/dashtrotaggedlatestand the commit SHA.
That's it — this repo doesn't deploy anywhere itself. Whatever consumes the image (e.g. the portfolio project) is responsible for pulling and running it.
Tests
npm test # everything: frontend, backend + cms_mcp, sdk/mcp
npm run test:frontend # cms-frontend (vitest)
npm run test:backend # cms_backend + cms_mcp (pytest, SQLite by default)
TEST_DB_TYPE=postgres pytest # cms_backend, against a reachable Postgres
npm run test:mcp # sdk/mcp — @dashtro/mcp (vitest)
License
Release files for dashtro 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dashtro-0.2.0.tar.gz | 45.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dashtro-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 95.7 kB
Release files / dashtro-0.2.0.tar.gz
| Download URL | dashtro-0.2.0.tar.gz |
|---|---|
| Size | 45.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
71739a85b8b05158fc3b06c9954a6e599fc736c7b61384a305db2391f95d9ae2
|
|
BLAKE2b-256 checksum How to use checksums |
df4ec9fe8ffc0ec3ba5f7f38a1bdd982b945d5b0c37ecb9cb821d4e45f8b6412
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 15, 2026.
Transparency logRelease files / dashtro-0.2.0-py3-none-any.whl
| Download URL | dashtro-0.2.0-py3-none-any.whl |
|---|---|
| Size | 50.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
484c09c0f8ba28db88cea8894e7a36f38c9d76da5be0488d1bf0faa700611117
|
|
BLAKE2b-256 checksum How to use checksums |
f7270957f13adea778dfa4598e0bbf53d7222b02421191d31ef14ab48d0ea21d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 15, 2026.
Transparency log