This is the collection of services that allow any individual to self-host their own instance of the pluto server. It can be used to store, analyze, visualize, and share any data recorded by the latest pluto clients or other platforms. It's super easy to get started and we welcome you to try it yourself! All you need is a containerized environment and a minute to spare.
For a managed instance with better scalability, stability and support, please visit pluto.trainy.ai or contact us at founders@trainy.ai.
- Docker with Compose v2 (the
docker composecommand). This comes with Docker Desktop or can be installed separately via the Compose plugin.
git clone https://github.com/Trainy-ai/pluto-server.git
cd pluto-servercp .env.example .envdocker compose --env-file .env up --build -dThe server will be swiftly available at http://localhost:3000.
- Open
http://localhost:3000and create an account - Go to
http://localhost:3000/api-keysand generate an API key - Install the Python SDK and run the integration test:
pip install pluto-ml
TEST_LOCAL=true python3 tests/e2e/integration_test.pyWhen prompted, paste your API key. The test will log metrics, a table, and finish a run. If it completes successfully, your deployment is working.
- a custom frontend application hosted on
port 3000 - a custom backend application hosted on
port 3001 - a Rust server for high-performance data ingestion on
port 3003 - a Python server for general-purpose health monitoring on
port 3004 - an S3-compatible storage server on
port 9000 - a ClickHouse database on
port 9000(not exposed to host by default) - a PostgreSQL database on
port 5432(not exposed to host by default)
Connecting the Python SDK to your self-hosted server:
pluto.init(settings={"host": "localhost"}) # or a specified host matching the CORS policy of the server set by .env502 Bad Gateway on first load:
On a fresh start, the frontend (nginx) may start before the backend is ready. Nginx caches the failed connection and keeps returning 502 even after the backend comes up. Restart the frontend container to fix it:
docker compose --env-file .env restart frontendRebuilding from scratch:
If you encounter stale build issues, rebuild without cache:
docker compose --env-file .env build --no-cache
docker compose --env-file .env up -dPrisma migrations (web/server/prisma/migrations/) normally apply themselves —
you should rarely need to run one by hand:
- Docker Compose: the backend entrypoint runs
prisma migrate deploybefore starting the server, sodocker compose upwith a new image applies any pending migrations. - Kubernetes (terraform): the
prisma-migratejob (terraform/modules/kubernetes/init-job.tf) runsnpx prisma migrate deployfrom the backend image, and each backend rollout re-runs it on startup.
migrate deploy is idempotent: it only applies migrations not yet recorded in
the _prisma_migrations table, so re-running it is always safe.
Running a migration manually (e.g. applying a merged migration to prod before the backend release rolls out):
From a backend image that contains the migration:
docker run --rm -e DATABASE_URL='postgresql://USER:PASS@HOST:5432/postgres' -e DATABASE_DIRECT_URL='postgresql://USER:PASS@HOST:5432/postgres' asaiacai/mlop-backend:<tag> npx prisma migrate deployOr with a slim node container and a git checkout — useful when no backend
image with the migration has been published yet (the schema + migrations are
mounted straight from the repo; libc6-compat/openssl are required by the
Prisma engines on alpine):
docker run --rm -v "$PWD/web/server/prisma":/work/prisma -w /work -e DATABASE_URL='postgresql://USER:PASS@HOST:5432/postgres' -e DATABASE_DIRECT_URL='postgresql://USER:PASS@HOST:5432/postgres' node:20-alpine sh -c 'apk add --no-cache libc6-compat openssl >/dev/null && npx -y prisma@5.2.0 migrate deploy'(Pin prisma to the version in web/server/package.json.)
On a cluster, the equivalent one-off is cloning the existing job:
kubectl -n mlop create job prisma-migrate-manual --from=job/prisma-migrate— noting that the cloned job runs the image tag currently referenced by the job spec, which must already contain the new migration.
You should be aware of all your data stored on the server. That's why the contents of the databases are mapped to directories on the host by default. When you need to migrate the server to a different host, simply make sure you take the .pluto folder and .env file with you.
We welcome any contributions to the project! Please feel free to submit any code, docs, feedback, or examples.
When developing locally with Docker Compose, simply run:
docker compose --env-file .env up --buildThe frontend container runs Nginx which serves the static build and proxies API requests (/trpc and /api) to the backend container via Docker's internal network (http://backend:3001). This is configured in web/app/nginx.conf.