# Phone Buzz — Docker deployment behind cPanel Apache

Prepared 22 October 2026. This is the server-only recipe for `phonebuzz.onlinetestingserver.com`. It preserves the host's existing Apache/TLS service on ports 80/443. It replaces the standalone Caddy recipe for this installation; do not merge `compose.yml` with `compose.cpanel.yml`.

## Files and architecture

- `deploy/staging/web.Dockerfile`: Node 24 multi-stage build; locked web/contracts dependencies; non-root standalone Next.js runtime.
- `deploy/staging/api.Dockerfile`: Python 3.14, locked dependencies, non-root API and migration runtime.
- `deploy/staging/compose.cpanel.yml`: PostgreSQL 17, explicit migrations, API, web and optional disabled AI worker. Only web publishes `127.0.0.1:18080`; API/database have no host ports. No Caddy container or new port 80/443 binding.
- `deploy/staging/compose.scan.yml`: required scanner socket mount for file handling.
- `deploy/staging/prepare-cpanel.py`: generates fresh private secrets, Compose settings and per-domain Apache includes. Refuses an existing settings directory.

Request path: browser HTTPS → existing Apache (TLS + staff IP restrictions) → localhost web port → private API → private PostgreSQL. The browser uses the web application's same-origin API proxy. No public API domain is required while mobile is deferred. The generated `api.internal.invalid` value is an internal configuration placeholder, not a DNS record to create.

## 1. Operator prerequisites

An authorized hosting administrator must perform deployment. The supplied cPanel account could run the Docker client and Compose 2.27.0, but Docker daemon access returned **permission denied**. Client 26.1.3 was observed; the daemon version could not be read. Do not change Docker socket permissions to world-writable or grant broad Docker privileges to resolve this casually.

Confirm:

- An accessible Linux Docker Engine and Compose v2.24+; check `docker version` and `docker compose version`. Python 3.10+ is needed for the settings generator; the API image supplies its own Python.
- A maintained, patched Docker Engine, version 28+ for the documented localhost publishing isolation behavior. Older Engines have a documented same-L2 reachability issue even for localhost-published ports. If upgrading is not possible, the operator must implement and independently verify equivalent network isolation before running this recipe. The client version does not prove the daemon version.
- Port 18080 is free (`ss -ltn 'sport = :18080'`); choose another unprivileged port if needed. All host users with local access must be trusted, or the operator must provide tenant isolation/a separate VPS: localhost binding alone is not an isolation boundary between shared-hosting users.
- The existing cPanel domain has valid HTTPS and the operator can install per-domain Apache includes. Required modules: proxy, proxy_http, headers, authz_core, authz_host and rewrite. Check with `apachectl -M`. Do not stop Apache or replace the server-wide configuration.
- Actual office/VPN CIDRs for staff access, the responsible administrator's email, capacity/disk monitoring and backups. Do not use `0.0.0.0/0` or `::/0`.
- A maintained ClamAV daemon/signature updater with `/var/run/clamav/clamd.sock` reachable by container UID/GID 10001. See PILOT-HARDENING.md for socket permissions, user-namespace mapping, limits, signature monitoring and probes. Do not disable SELinux or the host firewall; have the operator configure the narrow required rules.

The earlier shared-hosting account inspection does not establish adequate tenant isolation, Docker privileges or production capacity. If these cannot be provided, use a separate application VPS.

## 2. Extract the reviewed release

Verify the supplied `.sha256` against the trusted handoff value. Extract into a new private directory outside `public_html`, for example `/srv/phonebuzz/releases/cpanel-20261022-01/`. Preserve the `phonebuzz-platform` layout and run the remaining commands from that directory. Do not extract over the existing PHP website or an existing installation.

The server ZIP contains both Dockerfiles, complete runtime source, migrations, locks and deployment tools. The retained mobile `package.json` is workspace build metadata only; no mobile app is deployed. Docker builds install only the web/contracts workspace selection. Installed dependencies, private data and credentials are excluded.

## 3. Generate settings once

Replace the example certificate contact and documentation-only CIDR below with approved values. Repeat `--allow-cidr` for additional networks. The TLS contact is retained in shared settings; Apache/cPanel continues to manage the real certificate.

```sh
python3 deploy/staging/prepare-cpanel.py \
  --web-host phonebuzz.onlinetestingserver.com \
  --tls-email operator@example.com \
  --allow-cidr 203.0.113.7/32 \
  --web-port 18080 \
  --release cpanel-20261022-01
```

This creates `.local/staging/compose.env`, `integrations.env`, four secret files and `apache-ssl.conf`/`apache-http.conf`. It does not install Apache rules, start containers or print secrets. Protect `.local/staging` and its `secrets` subdirectory with mode 0700. Environment/snippet files use 0600; secret files use 0444 inside the protected directory so the selected containers can read their mounted files. Preserve this boundary. Never regenerate secrets over an existing database/MFA installation.

## 4. Validate, build and start privately

Define a helper in the current shell; every command below uses the cPanel file and scanner overlay together:

```sh
dc() {
  docker compose --env-file .local/staging/compose.env \
    -f deploy/staging/compose.cpanel.yml \
    -f deploy/staging/compose.scan.yml "$@"
}
dc config --quiet
dc build api web
dc up -d --wait db
dc run --rm --no-deps migrate
dc up -d --wait api web
dc ps -a
```

**Stop on any failed command.** Migration success is mandatory before starting application writers. The dependency graph can repeat the idempotent migration/grant step at first `up`; the explicit migration command above gives the operator a clear exit status. Expected schema head: `64fbd353a9d4`. No public ports are added except the selected IPv4 loopback web port. Run `docker ps` and `ss -ltn` to confirm the actual bindings.

Do not run `docker compose up` from the root development Compose file. Do not add the original `compose.yml` as an overlay. Keep live email, WhatsApp, push, formal invoice issuance and learning disabled initially. The worker is not started by the default profile.

Before adding browser access:

```sh
curl --fail --silent --show-error -o /dev/null http://127.0.0.1:18080/
dc exec -T api python -c "import urllib.request; print(urllib.request.urlopen('http://127.0.0.1:8000/ready', timeout=8).status)"
dc run --rm --no-deps -T api python -m phonebuzz.scan_probe --run
```

Use the configured port if it differs from 18080. The scanner probe sends an in-memory harmless EICAR test marker and clean sample; it must accept clean input and reject the marker. Readiness alone does not verify scanning, staff workflows or backups. Record image digests after building; tags alone are not immutable release identifiers.

## 5. Install per-domain Apache routing

The generated files are **VirtualHost include snippets**, not `.htaccess` files and not complete replacement Apache configurations. The hosting administrator must review existing per-domain rewrites/proxy/cache rules and back up affected configuration first. If the target include already exists, stop and review it instead of overwriting it. Do not place includes at server-wide or account-wide scope.

For this account, the proposed destinations are:

```text
/etc/apache2/conf.d/userdata/ssl/2_4/phonebuzzonlinet/phonebuzz.onlinetestingserver.com/phonebuzz.conf
/etc/apache2/conf.d/userdata/std/2_4/phonebuzzonlinet/phonebuzz.onlinetestingserver.com/phonebuzz.conf
```

Install `.local/staging/apache-ssl.conf` at the SSL path and `.local/staging/apache-http.conf` at the standard HTTP path. The HTTP include redirects to the fixed HTTPS hostname, leaving ACME/PKI validation paths with cPanel. The HTTPS include enforces the generated IP allowlist, replaces untrusted forwarded headers, preserves the application's download/CSP headers and disables Apache caching when mod_cache is loaded. Keep CDN/proxy caching and HTML rewriting off for this workspace. If another proxy/CDN is in front, this recipe's client-IP trust assumptions require redesign and testing first.

After creating/reviewing the two includes, the administrator runs:

```sh
/usr/local/cpanel/scripts/rebuildhttpdconf
apachectl -t
/usr/local/cpanel/scripts/restartsrv_httpd
```

Run each command separately and stop on errors. Restart only after a successful syntax check, within the operator's approved window. If validation fails, restore the affected include backup, rebuild and validate again before restarting. Verify both the intended domain and other hosted sites. Confirm HTTP redirection and future AutoSSL renewal; custom includes can affect cPanel's Force HTTPS Redirects setting.

## 6. Create accounts and test

Create named staff administrators after readiness succeeds:

```sh
dc run --rm --no-deps api python -m phonebuzz.cli create-admin \
  --email admin@example.com --name "Staging administrator"
```

Replace the example identity; enter the new password privately at the prompt. Create a second separately named administrator for quote/order approval tests. No existing website accounts are imported. Visit `https://phonebuzz.onlinetestingserver.com` from an allowed office/VPN network and enroll MFA.

Acceptance checklist:

- Valid TLS, working login/logout/MFA and secure cookies; unexpected browser origins rejected.
- Allowed network can reach the app; a genuinely different denied network gets 403, including when it sends spoofed forwarded headers. The loopback app port is inaccessible from external networks, including the host's L2 segment; API/database ports are not published.
- Root documents have fresh CSP nonces/no-store behavior; downloaded files retain their sandbox policy. Check no global hosting cache/filter changes these responses.
- Synthetic company/stock/wanted matching, quote approval, order confirmation, IMEI dispatch/returns, viewer restrictions and files all work.
- Scanner outage blocks file operations; recovery restores them. Verify signature freshness separately.
- Coordinated PostgreSQL/files/MFA-key backup and isolated restore succeed before real records are introduced.

Send sanitized URLs, release/image identifiers, readiness/scan/access results and reproducible issues back to development. Do not paste secrets, resolved secret-bearing configuration or customer records into tickets.

## 7. Updates, backups and recovery

Use STAGING.md's coordinated backup/restore procedure, replacing every `-f deploy/staging/compose.yml` with `-f deploy/staging/compose.cpanel.yml -f deploy/staging/compose.scan.yml`. Omit the nonexistent `proxy` service from commands; Apache is managed separately. Preserve the runtime settings, database and attachment volumes, and original MFA key. Keep the project name `phonebuzz-staging` consistent across releases; do not reuse it for an unrelated installation. An isolated restore requires a different project name and unused loopback port, with no public Apache routing.

Before updates: record current images/schema, stop writers, take and verify an encrypted off-server coordinated backup, then migrate explicitly. Change only the release image tag in the existing private settings, not the passwords or MFA key. On migration failure, keep writers stopped and investigate. Do not run `down -v`, regenerate secrets, delete volumes or blindly roll back images across schema changes.

Optional AI hosting follows SERVER-AI.md after the base web/API pilot passes. Substitute the cPanel base file in those commands and retain the scanner overlay. Model sizing, provider access and real evaluation remain separate work. Mobile distributions are deferred.

## Local verification and remaining work

54 focused configuration/setup/archive tests passed. A disposable local Apache HTTP harness verified snippet syntax, allowed/denied IPs, spoofed forwarded headers, preservation of downstream CSP/no-store, the fixed-host HTTPS redirect and the certificate-validation exception. The harness supplies synthetic backend responses; it does not test the target's TLS or cPanel integration. Initial harness attempts required local runtime-directory/process-group adjustments; the final run passed and cleaned up its processes.

Docker Engine is unavailable locally, and the supplied server account cannot access its daemon. Linux image builds, actual container startup, installed cPanel includes/TLS, resource limits, shared-host isolation, backup/restore and hosted acceptance remain unverified. No server settings, credentials or live data were changed to prepare this handoff. Review retained dependency and release-readiness documents for the broader product limits.

References: [cPanel per-domain includes](https://docs.cpanel.net/ea4/apache/modify-apache-virtual-hosts-with-include-files/), [Docker published-port behavior and older-version caveat](https://docs.docker.com/engine/network/port-publishing/).
