Requirements
Licensing, at a glance
- Entra ID P1 or P2 — required for the two home-tenant access groups PatchPilot365 creates (bundled in Microsoft 365 Business Premium, EMS E3/E5, or Microsoft 365 E3/E5). Without it, a Global Administrator can still use PatchPilot365 directly (see below) — it only blocks the optional delegation groups.
- Global Administrator or Privileged Role Administrator — to run the deploy/pairing script, and to grant or revoke another engineer’s write access.
- Microsoft Intune — for app deployment, on-demand remediation, and quality/feature update campaigns (bundled in Microsoft 365 Business Premium or Microsoft 365 E3/E5).
- Microsoft Defender for Business, or Defender for Endpoint Plan 1+ — for Live Response (bundled in the same plans above).
- A GDAP relationship via Microsoft Partner Center — before PatchPilot365 can reach a customer tenant at all. PatchPilot365 cannot create this relationship itself; it always starts in Partner Center, where the MSP requests it and the customer approves it.
- Membership in the home tenant’s
AdminAgentsgroup — needed to discover existing GDAP relationships during onboarding, in addition to holding Global Administrator. - Live Response and Unsigned Scripts enabled manually, per tenant — two toggles in the Microsoft 365 Defender portal (Settings > Endpoints > Advanced features) that no API or PowerShell cmdlet can set. A Global Administrator or Security Administrator has to enable them by hand, once per customer tenant, before Live Response will work there.
Without the Entra ID P1/P2 license specifically: PatchPilot365’s two optional
delegation groups (PatchPilot Read-Only Access and
PatchPilot Write Access) can’t be created, but a genuine Global
Administrator — or anyone directly assigned Global Reader, Security Reader,
Security Administrator, Intune Administrator, and/or Windows Update
Deployment Administrator — can use PatchPilot365 exactly the same way with no
Entra P1/P2 at all. The groups only exist to delegate access to other
engineers without making them Global Administrator outright.
Roles inside PatchPilot365
Separate from all of the above: once PatchPilot365 can reach a tenant, what an individual signed-in engineer can do inside PatchPilot365 is governed by their PatchPilot365 role, not their Entra/GDAP role. Three roles:
| Role | Can do |
|---|---|
| Admin | Everything — including managing users, catalogs, and tenant settings. |
| Technician | Read everything and run remediation. Cannot manage users, scripts, or settings. |
| Reader | Read-only across the whole console. Cannot run or change anything. |
See Manage users and roles for how to assign these, and the role breakdown by product area on Settings > Users > Roles inside the app.
Software and tooling (Manual Deployment)
Only relevant if you’re self-hosting via Manual Deployment — Azure Deployment provisions all of this automatically.
- Docker and Docker Compose on the host that runs PatchPilot365.
- Node.js 24+ and pnpm 10+ if you’re building/running outside the provided Docker images (contributor/development workflows).
- PostgreSQL and Redis — provisioned automatically by the Docker Compose files; no manual setup needed if you use them as shipped.
Network requirements
See Architecture: Network requirements at a glance.
Environment variables reference
Every setting lives in .env, copied from .env.example at setup time.
Everything Entra-related is deliberately left blank until pairing completes
— filling in a fake placeholder value breaks the “Pair this instance” flow,
since PatchPilot365 treats “all three set” and “all three blank” as the only
two valid states before pairing.
| Variable | Purpose |
|---|---|
PUBLIC_URL |
The public HTTPS origin where PatchPilot365 is reached. |
PP_DOMAIN |
Bare hostname Caddy serves and requests a Let’s Encrypt certificate for. |
AUTH_REDIRECT_URI |
Entra app redirect URI — must exactly match the app registration. |
ENTRA_TENANT_ID / ENTRA_CLIENT_ID / ENTRA_CLIENT_SECRET |
Leave blank until pairing. A paired instance keeps them in its own database, with the credential encrypted, and the pairing script blanks them in .env afterwards. ENTRA_CLIENT_SECRET is only needed for an install configured by hand, without pairing, that uses a client secret. |
ENTRA_CREDENTIAL_TYPE / ENTRA_CERTIFICATE_PRIVATE_KEY / ENTRA_CERTIFICATE_THUMBPRINT_SHA256 |
Optional. secret (default), certificate or managed-identity. A paired instance sets these itself from the certificate it generated during pairing; only set them by hand to use your own certificate. managed-identity is for Azure VMs only; see Deploying to an Azure VM. |
ENTRA_MANAGED_IDENTITY_CLIENT_ID |
Azure only. The client ID of the VM’s user-assigned managed identity; the Azure deployment sets it. With it set, a downloaded pairing script pairs with that identity. Entra doesn’t accept a system-assigned identity as an app credential. |
ENTRA_KEY_VAULT_URL / ENTRA_KEY_VAULT_SECRET_NAME / ENTRA_KEY_VAULT_CERTIFICATE_NAME |
Optional, Azure only. Read the client secret, or sign with a certificate, kept in Azure Key Vault, using the VM’s managed identity. Name a secret for secret or a certificate for certificate. A Key Vault credential wins over a local or paired one of the same type. |
SESSION_SECRET / TOKEN_ENCRYPTION_KEY |
32-byte base64 keys — generate with openssl rand -base64 32. |
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB / POSTGRES_HOST / POSTGRES_PORT / DATABASE_URL |
Database connection. |
REDIS_HOST / REDIS_PORT / REDIS_URL |
Job queue and token cache backing. |
REDIS_PASSWORD |
Optional, Docker Compose only. Turns on Redis authentication. New installs (Azure deploy and Deploy-PatchPilot.ps1) generate one; to add one to an existing install, set a random hex string (openssl rand -hex 32) and run docker compose up -d again. |
API_PORT |
Port the Fastify API listens on. |
CORS_ORIGINS |
Comma-separated origins allowed to call the API — the web app’s own origin. |
AUTO_SYNC_INTERVAL_MINUTES |
How often the API background-refreshes devices/vulnerabilities for reachable tenants. 0 disables it. Ignored in Demo Mode. |
UPDATE_CHECK_INTERVAL_HOURS |
How often the API polls GitHub Releases for a newer PatchPilot365 version. 0 disables it. |
BOOTSTRAP_ADMIN_UPN |
The UPN that’s seeded/promoted to an active admin on every startup — both how you provision the very first admin and how you recover if you lock yourself out. Safe to leave set permanently; the upsert is idempotent. |
DEMO_MODE |
true (default) runs with zero dependencies and bypassed auth. Set false for production, which then requires every secret/URL above to be genuinely set. |
AI_FEATURES_ENABLED |
Off by default. See AI features. |
OLLAMA_BASE_URL / OLLAMA_MODEL |
Self-hosted AI model connection and model name. |
REPORT_RETENTION_DAYS / REPORT_RETENTION_MAX_PER_ENGINEER |
How long generated reports are kept, and a per-engineer cap. |
REPORT_BROWSER_EXECUTABLE_PATH / REPORT_BROWSER_CHANNEL |
How the worker finds a Chromium/Edge binary to render report PDFs. On Windows dev boxes, set REPORT_BROWSER_CHANNEL=msedge to reuse your existing Edge install. |
REPORT_PDF_TIMEOUT_MS |
Timeout for a single report’s PDF render. |
SMTP_HOST and related SMTP_* vars |
Fallback failure-alerting relay. Superseded by the in-app Settings > Notifications page for most deployments. |
BACKUP_RETENTION_DAYS / BACKUP_HOUR |
Nightly Postgres backup retention and schedule. See Server Health & Maintenance. |
The full, fully-commented reference is
.env.example
in the repository — copy it as a starting point rather than retyping values
from this table.