Version: 1.0 | Last Updated: 02 June 2026 | Xzopia Limited (NI620366)
⛔ SUPERSEDED — 06 June 2026
The self-hosted Docker deployment described in this document was attempted in June 2026
and abandoned due to two fundamental blockers:
- Azurite API version lag breaks CIPP's Azure Durable Functions SDK (API version
2026-02-06rejected by Azurite 3.35.0)- CIPP's credential flow (ApplicationSecret + RefreshToken) is tightly coupled to the SAM wizard — manual injection fails with AADSTS7000215 and RefreshToken truncation
Use
docs/cipp-azure-deployment.mdinstead. The official Azure route (SWA + Functions + Key Vault + Storage) is the only supported deployment path.See LESSONS_LEARNED.md #55–58 for the full technical post-mortem.
This document covers the full deployment of CIPP (CyberDrain Improved Partner Portal) on
Xzopia's own VPS infrastructure, including architecture decisions, security requirements,
Cyber Essentials 2026 compliance, Claude Code skill integration, and the week-by-week
execution roadmap.
CIPP CAN run on a Xzopia VPS — fully, without Azure. This is not a workaround; the
CIPP-API repository ships an official docker-compose.yml designed for exactly this. The
project uses:
cippapi container — Azure Functions runtime (PowerShell) compiled into Dockerazurite container — Official Microsoft Azure Storage emulator (replaces Azure Storage)No Azure subscription required. No ongoing Azure cost. All data stays on Xzopia
infrastructure. This is the approach this plan implements.
Important: Azurite stores CIPP configuration and partner credentials (access tokens for
70 client tenants) in local table storage. These must be protected with disk encryption,
strict access controls, and encrypted backups. This document covers all three.
CIPP must NOT share a server with existing services. Reasons:
Server 6 specification:
| Setting | Value |
|---|---|
| Provider | Fasthosts |
| OS | Ubuntu 24.04 LTS |
| Spec | 2 vCPU, 4GB RAM, 40GB SSD (minimum) |
| IP | TBC (assign after provisioning) |
| SSH port | 2222 (consistent with all servers) |
| Domain | cipp.xzopiasecure.com |
| Reverse proxy | Nginx |
| Runtime | Docker + Docker Compose |
Add to server inventory table in PROJECT_STATUS.md after provisioning.
Internet → UFW → Nginx (443/HTTPS) → cippapi (127.0.0.1:7071)
→ CIPP frontend static (127.0.0.1:4280)
Container network (cipp-net, internal only):
cippapi ← → azurite (127.0.0.1:10000-10002)
Data:
Docker volume: cipp-azurite-data (on encrypted disk partition)
Backup: Restic → Backblaze B2 (encrypted, daily)
All container ports bound to 127.0.0.1 — never directly exposed. Only Nginx terminates
external traffic on 443. Port 80 redirects to 443.
| Service | Container | Port (internal) | Role |
|---|---|---|---|
| cippapi | Azure Functions PowerShell | 127.0.0.1:7071 | CIPP API backend |
| azurite | Azure Storage emulator | 127.0.0.1:10000-10002 | Config + credential storage |
| cipp-frontend | Nginx (static files) | 127.0.0.1:4280 | React UI |
Azurite's data volume holds decrypted partner credentials at rest. Encrypt the Docker
volumes directory before deploying containers:
# Create encrypted partition for Docker volumes
# Use ecryptfs or LUKS on /var/lib/docker/volumes or a dedicated mount
Without disk encryption, a physical disk compromise exposes all 70 client credentials.
cipp-net bridge, no host networkingEffective April 2026. Auto-fail questions are marked ⛔.
| Requirement | Implementation | Status |
|---|---|---|
| Internet-facing firewall active | UFW default deny, rules for 443+2222 only | ✅ |
| Default passwords changed | No default admin passwords | ✅ |
| Inbound connections blocked by default | UFW default deny | ✅ |
| Unused rules removed | Only 443 and 2222 open | ✅ |
| Admin interface not accessible from internet | Nginx has no admin interface exposed | ✅ |
| Requirement | Implementation | Status |
|---|---|---|
| Default software removed/disabled | Minimal Ubuntu install, Docker only | ✅ |
| Auto-start disabled for unused services | Only CIPP containers and Nginx autostart | ✅ |
| No unnecessary accounts | Single xzopia user, no guest accounts |
✅ |
| Server software tokens hidden | server_tokens off in Nginx |
✅ |
| Security headers | HSTS, X-Frame-Options, X-Content-Type-Options, CSP | ✅ |
| Requirement | Implementation | Status |
|---|---|---|
| ⛔ MFA enabled on all cloud services | Entra ID Conditional Access forces MFA on CIPP — auto-fail if not configured | Must configure |
| Accounts with admin privileges minimised | Only Xzopia staff in CIPP Entra app | ✅ |
| Separate admin accounts | CIPP service account separate from personal accounts | ✅ |
| Access removed when no longer needed | Entra ID group membership managed | ✅ |
| Strong passwords | Entra ID password policy + MFA | ✅ |
| Requirement | Implementation | Status |
|---|---|---|
| Anti-malware active | ClamAV installed and scheduled | ✅ |
| Docker images scanned | Trivy scan before deployment | Must implement |
| Sandbox/containment | Docker network isolation | ✅ |
| Requirement | Implementation | Status |
|---|---|---|
| ⛔ Critical OS patches within 14 days | unattended-upgrades enabled — auto-fail if not active | Must verify |
| ⛔ Critical app patches within 14 days | Weekly Docker image rebuild (cron job) — auto-fail if not active | Must implement |
| End-of-life software removed | Ubuntu 24.04 LTS supported until 2029 | ✅ |
| Firmware updates applied | Fasthosts manages hypervisor | ✅ |
Mandatory before CE assessment:
unattended-upgrades confirmed active and testedEvery skill in the Claude Code toolkit has a role in this deployment. The 6-stage pipeline
gates progression.
| Stage | Skill(s) | Purpose |
|---|---|---|
| 0 — Server prep | Manual (13-step checklist) | Provision Server 6 |
| 1 — Pre-flight | /pre-flight-check |
Verify Server 6 ready before any deployment |
| 2 — Spec & plan | /spec-first, /writing-plans |
CIPP deployment spec + ordered plan |
| 2 — Install | /executing-plans, /dispatching-parallel-agents |
Deploy Docker Compose, Nginx, SSL |
| 3 — Security review | /security-review |
Review Nginx config, Docker setup, auth |
| 4 — Post-install audit | /audit-server |
Verify all services healthy, no regressions |
| 5 — Pentest | /security-pentest |
Lynis ≥80/100, nmap, nikto on CIPP surface |
| 5 — TDD verification | /tdd, /verification-before-completion |
Tests for CIPP API health, auth flow |
| 6 — Sign-off | /verification-before-completion, /karpathy-guidelines |
Confirm all checks pass, no shortcuts |
| Ongoing | /backup-server |
Before every change to the CIPP server |
Day 1 — Provision Server 6
/pre-flight-check → must return READYDay 2 — Security Baseline
/audit-server → confirm baselineDay 3 — Fork + Build
Day 4 — Frontend + Nginx
Day 5 — Security Review + Pre-Pentest
/security-review on Nginx config, Docker Compose, UFW rulesDay 1 — Entra ID App Registration
Day 2 — CIPP Initial Configuration
Day 3 — GDAP Management Setup
Day 4 — Standards Configuration
Day 5 — Pentest + Lynis
/security-pentest on Server 6Day 1-2 — Bulk Client Onboarding
Day 3 — Alerting + ITFlow Integration
Day 4 — Reporting
Day 5 — CE Pre-Assessment Audit
systemctl status unattended-upgradescrontab -l | grep dockerrestic -r <B2 repo> snapshotsDay 1 — Backup Verification
Day 2 — Docker Update Automation
docker compose up -dDay 3 — Documentation + Wiki
Day 4 — Final Security Pentest
/security-pentest — confirm Lynis ≥80/100/verification-before-completion — all gates must passDay 5 — Sign-Off
/verification-before-completion confirms:
Task: Provision and harden Server 6 for CIPP deployment. This is the dedicated CIPP server.
IP: [TBC — paste after provisioning]. Ubuntu 24.04. SSH port 2222. User: xzopia.
Follow docs/new-server-preparation.md (13-step checklist) exactly.
Then:
1. /pre-flight-check — must return READY before proceeding
2. Install Docker and Docker Compose (official Docker repo, not apt default)
3. UFW: default deny incoming, allow 2222 and 443 only
4. Install Fail2Ban, ClamAV (daily scan), unattended-upgrades (confirm active)
5. /audit-server — confirm baseline
DISK ENCRYPTION: Configure ecryptfs or LUKS encryption on the Docker volumes directory
(/var/lib/docker/volumes or a dedicated mount at /opt/cipp-data). CIPP stores partner
credentials here. This step is mandatory — do not proceed without it.
No CIPP containers yet — baseline only. Commit docs/server6-preflight.md with findings.
Security review all changes. Take Fasthosts snapshot after completion.
Task: Deploy CIPP on Server 6 (cipp.xzopiasecure.com, [IP]) using Docker Compose.
Do NOT use Azure — full VPS deployment using the official CIPP docker-compose.yml.
Server 6 only. Do not touch any other server.
1. /backup-server — snapshot before changes
2. Fork setup:
- Confirm Silentrunnerx GitHub org has forks of:
KelvinTegelaar/CIPP and KelvinTegelaar/CIPP-API
- Clone both forks to /opt/cipp/ on Server 6
3. Docker Compose deployment:
- Use the CIPP-API docker-compose.yml as the base
- Services: azurite (storage emulator) + cippapi (Azure Functions runtime)
- Bind ALL container ports to 127.0.0.1 — never expose to 0.0.0.0
- Create isolated Docker network: cipp-net
- Mount azurite data to the encrypted volume at /opt/cipp-data/azurite
- Confirm both containers start and communicate
4. CIPP frontend:
- Build React static files from the CIPP frontend fork
- Serve via Nginx on 127.0.0.1:4280
5. Nginx configuration:
- Reverse proxy: / → cippapi:7071, frontend on /
- SSL: certbot + Let's Encrypt for cipp.xzopiasecure.com
- Security headers: HSTS max-age=31536000, X-Frame-Options DENY,
X-Content-Type-Options nosniff, Referrer-Policy strict-origin-when-cross-origin
- server_tokens off
- Rate limiting: limit_req_zone for auth endpoints
6. /security-review — review Nginx config and Docker Compose
7. /audit-server — confirm all containers healthy, HTTPS operational
8. Trivy scan: trivy image [cippapi-image] and [azurite-image]
9. Commit: docker-compose.yml, nginx config, env template (no secrets) to repo
under deploy/server6/
10. Update PROJECT_STATUS.md session log, commit and push (wiki sync)
Task: Configure CIPP authentication and verify Cyber Essentials 2026 compliance on Server 6.
AUTHENTICATION:
1. In Xzopia partner tenant (Entra ID):
- Create app registration for CIPP (follow docs.cipp.app prerequisites)
- Configure redirect URIs to https://cipp.xzopiasecure.com/auth/callback
- Assign required Graph API permissions per CIPP documentation
- Create Conditional Access policy: require MFA for all sign-ins to the CIPP app
(This is an auto-fail item in Cyber Essentials 2026 Danzell — mandatory)
- Store app registration Client ID + Secret in Vaultwarden: "CIPP Entra App"
2. Complete CIPP setup wizard at https://cipp.xzopiasecure.com
3. Run CIPP Permissions Check — all green required before proceeding
4. Test login as Xzopia staff — confirm MFA prompt appears
CYBER ESSENTIALS 2026 VERIFICATION:
Run these checks and document pass/fail in docs/ce-compliance-server6.md:
Firewalls:
- sudo ufw status verbose — confirm only 443 and 2222
- nmap -sV --open [server-ip] — confirm no unexpected open ports
Secure Configuration:
- curl -I https://cipp.xzopiasecure.com — confirm security headers present
- grep server_tokens /etc/nginx/nginx.conf — confirm "off"
- docker inspect cippapi — confirm no privileged:true
User Access Control:
- Confirm Conditional Access policy requires MFA (show CA policy name/ID)
- Confirm no accounts with Global Admin in Xzopia tenant that aren't needed
Malware Protection:
- systemctl status clamav-daemon — confirm running
- cat /var/log/clamav/freshclam.log | tail -5 — confirm definitions updated
Security Update Management (AUTO-FAIL items):
- systemctl status unattended-upgrades — must be active
- cat /etc/apt/apt.conf.d/50unattended-upgrades — confirm security updates enabled
- crontab -l — confirm weekly Docker rebuild cron exists
- Provide evidence patch timeline is within 14 days
Commit docs/ce-compliance-server6.md and update PROJECT_STATUS.md. Push.
Task: Full security pentest on Server 6 CIPP deployment.
/backup-server first, then:
1. /security-pentest — run full suite:
- Lynis: target score ≥80/100. Document all findings.
- nmap -sV --open [server-ip]: confirm only 443 and 2222 externally reachable
- nikto -h https://cipp.xzopiasecure.com: check for web vulnerabilities
- Trivy image scan: all CIPP containers
2. Remediate all HIGH findings immediately. Document MEDIUM findings with mitigations.
3. For each HIGH finding, apply the fix and re-run the specific test to confirm resolved.
4. Update LESSONS_LEARNED.md with any new lessons from this pentest.
5. /verification-before-completion — final sign-off checklist:
- Lynis ≥80/100
- nmap: only 443 and 2222 open
- All containers running, 0 crash restarts
- HTTPS A-grade (test with testssl.sh or equivalent)
- Security headers present (verify via curl -I)
- No HIGH findings unresolved
6. Update PROJECT_STATUS.md: CIPP pentest results, Lynis score, findings summary.
Commit and push. Take final Fasthosts snapshot.
| Resource | URL |
|---|---|
| CIPP Documentation | https://docs.cipp.app |
| CIPP GitHub (Frontend) | https://github.com/KelvinTegelaar/CIPP |
| CIPP-API GitHub | https://github.com/KelvinTegelaar/CIPP-API |
| CIPP Discord | https://discord.gg/cyberdrain |
| CE Requirements v3.3 | https://www.ncsc.gov.uk/cyberessentials/overview |
| CE Danzell Question Set | https://iasme.co.uk |
| Trivy (Docker scanning) | https://github.com/aquasecurity/trivy |
| testssl.sh | https://github.com/drwetter/testssl.sh |
Xzopia Limited (NI620366) | simon@xzopia.com | Bangor, Northern Ireland