Manual Deployment
Manual Deployment is self-hosting: you provision the machine — your own Azure subscription without the ARM template, your own on-prem hardware, or a third-party host — and run PatchPilot365’s Docker Compose stack on it yourself. Use this if you don’t want a machine created and billed automatically the way Azure Deployment does it, or if your infrastructure has to live somewhere other than Azure.
Support does not extend to self-hosted deployments. You’re responsible for provisioning, securing, updating, and backing up your own infrastructure. For a supported, guided setup where we own the provisioning, use Azure Deployment instead.
There’s no from-scratch install script for this path yet — today, “manual deployment” means using the Docker Compose file yourself on any Ubuntu host you provide (a VM, bare metal, or a third-party host) and following the steps below. For full technical detail beyond what’s here, see the repository README.
Deploy the stack
Docker Compose brings up every service behind a reverse proxy that provisions its own TLS certificate.
git clone https://github.com/PatchPilot365/PatchPilot.git
cd PatchPilot
cp .env.example .env # set PP_DOMAIN, secrets, POSTGRES_PASSWORD, DEMO_MODE=false
docker compose -f infra/docker-compose.yml --env-file .env up -d --build
See Requirements for what every
.env variable does before you fill it in.
Services brought up: caddy (TLS via Let’s Encrypt) in front of web
(the compiled SPA served by nginx) and api (Fastify), plus worker,
postgres, redis, and ollama. Point your domain’s DNS at the host and
Caddy provisions the certificate automatically — no manual certificate
handling.
Once the stack is up, continue to Pairing to connect it to a real Microsoft 365 tenant.
Redirect URI
Real sign-in (the On-Behalf-Of token exchange) needs a reachable HTTPS
redirect URI that exactly matches what’s registered in the Entra app and
written in .env (AUTH_REDIRECT_URI / PUBLIC_URL / CORS_ORIGINS).
http://localhost is the only exception, for local testing.
A static host (including GitHub Pages) cannot run PatchPilot365 itself. Completing the OAuth code-for-token exchange happens server-side using the app’s credential (a certificate or client secret), which must never reach the browser. A static host can only serve the SPA shell — sign-in would be broken, since there’s no server there to hold the credential. You need a host that can run the Docker stack.
AI features (optional)
Off by default (AI_FEATURES_ENABLED=false). The model runs entirely inside
your own Compose stack — the ollama container has no published port and
makes no calls out to the internet at inference time.
# 1. Set in .env: AI_FEATURES_ENABLED=true
# 2. Bring the stack up (or restart it) so the ollama container exists
docker compose -f infra/docker-compose.yml --env-file .env up -d
# 3. One-time model pull — needs host internet access for this step only,
# never again after. Weights persist in the ollama_data volume.
docker compose -f infra/docker-compose.yml --env-file .env exec ollama ollama pull llama3.1:8b
OLLAMA_MODEL defaults to llama3.1:8b (Meta, USA) — deliberately not a
PRC-origin model, since vendor provenance for the model weights matters
independently of network isolation when the box handles MSP client security
data. Minimum recommended: 8 vCPU / 16GB RAM. On beefier hardware, pull
llama3.1:70b instead and set OLLAMA_MODEL to match — no code change
needed, though a GPU is strongly recommended at that size.
Useful scripts
| Command | What it does |
|---|---|
pnpm build |
Build all packages |
pnpm db:generate |
Generate a Drizzle migration from the schema |
pnpm db:migrate |
Apply migrations |
pnpm db:seed |
Seed demo tenants/devices/vulnerabilities |
pnpm db:seedunconditionally wipes and reloads the database’s demo tables. Never run it against an instance holding real onboarded tenants.
For a contributor/development workflow (local hot reload, running against dev Postgres/Redis, the full test suite), see the repository README instead — this page only covers standing up a real deployment.