# Docker Deployment Guide

This guide is for deploying the packaged Zimphony webmail Docker bundle on a
customer server. It applies to the release zip created by the packaging scripts,
for example:

```text
zimphony-webmail-free-YYYYMMDD-HHMMSS.zip
zimphony-native-zimbra-business-100-YYYYMMDD-HHMMSS.zip
```

For a complete new-VM customer runbook that includes system requirements,
backup, update, and support policy, see
[`customer-vm-installation.md`](customer-vm-installation.md).

The bundle contains one root `install.sh` entrypoint. The selected edition is
locked by the package, so the customer does not need to choose an edition during
installation.

Free Webmail packages are license-free. They do not require a License Server,
Zimphony license key, license signing secret, automated payment flow, or LOR
connection at runtime.

## What Gets Installed

The Docker bundle starts:

- `backend`: FastAPI backend and Admin API on container port `8000`
- `frontend`: Next.js webmail UI and customer Admin UI on container port `3000`
- `backend-data` Docker volume for runtime data

The customer Docker package uses these two application containers only. There is
no separate `admin-frontend` container; the Admin UI is served by the same
frontend container at `/admin`.

The `backend-data` volume persists:

- local SQLite platform database
- uploaded tenant branding files
- tenant branding manifests

The web UI includes:

- webmail at `/`
- customer admin at `/admin`
- backend API proxied through `/api`
- customer admin API proxied through `/api/admin` by the frontend API routing

## Required Inputs

Prepare these values before installing:

- Webmail host, for example `mail.example.com`
- Mail server SOAP URL, for example
  `https://zimbra.example.com/service/soap`
- Mail server admin email
- Mail server admin password
- Brand name
- Brand tagline
- Optional logo URL
- Optional favicon URL
- Customer admin help text, for example `Help : support@example.com`

For Free Webmail packages, leave these license fields blank if prompted or
preseeded by automation:

- `LICENSE_SERVER_URL`
- `ZIMPHONY_LICENSE_KEY`
- `LICENSE_CHECK_SIGNING_SECRET`

Paid package license values are only required for paid editions.

## Network Requirements

The Docker host must be able to reach the mail server SOAP endpoint.

Expose the frontend to users:

- Container `frontend` listens on host port `3000` by default.
- Container `backend` listens on host port `8000`.

For production, put a reverse proxy such as Nginx, Caddy, Traefik, Cloudflare
Tunnel, or a load balancer in front of the frontend.

Recommended public routing:

- `https://mail.example.com/` -> Docker host `127.0.0.1:3000`
- `https://mail.example.com/admin` -> Docker host `127.0.0.1:3000`
- `/api/*` is proxied internally by Next.js to the backend container

Do not expose port `8000` publicly unless the deployment requires it. The
frontend can proxy API requests to the backend inside Docker.

## Linux Install

### 1. Install Docker

On Ubuntu/Debian, install Docker Engine and Compose plugin:

```bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg unzip
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```

Confirm:

```bash
docker --version
docker compose version
```

### 2. Upload and Extract the Bundle

Copy the release zip to the server, then:

```bash
mkdir -p /opt/zimphony
cd /opt/zimphony
unzip /path/to/zimphony-webmail-free-YYYYMMDD-HHMMSS.zip
cd zimphony-webmail-free-YYYYMMDD-HHMMSS
chmod +x install.sh deploy/docker/install.sh deploy/docker/mail-only/install.sh
```

### 3. Interactive Install

Run:

```bash
./install.sh
```

The installer asks for:

- webmail host
- SOAP URL
- admin email/password
- branding values
- customer admin help text

It writes:

```text
deploy/docker/mail-only/backend.env
deploy/docker/mail-only/frontend.env
```

Then it runs:

```bash
docker compose -f deploy/docker/mail-only/docker-compose.yml up -d --build
```

### 4. Non-interactive Install

Use this for scripted deployments:

```bash
NONINTERACTIVE=1 \
WEBMAIL_HOST=mail.example.com \
ZIMBRA_SOAP_URL=https://zimbra.example.com/service/soap \
ZIMBRA_ADMIN_SOAP_URL=https://zimbra.example.com:7071/service/admin/soap \
ZIMBRA_ADMIN_EMAIL=admin@example.com \
ZIMBRA_ADMIN_PASSWORD='change-me' \
BRAND_NAME='Zimphony' \
BRAND_TAGLINE='Business email that grows with you' \
ADMIN_HELP_TEXT='Help : support@example.com' \
./install.sh
```

For paid packages, also preseed the license values:

```bash
LICENSE_SERVER_URL=https://license.zimphony.com
ZIMPHONY_LICENSE_KEY='<license-key>'
LICENSE_CHECK_SIGNING_SECRET='<license-check-signing-secret>'
```

Do not set these license values for the Free Webmail package; Free is
license-free and does not contact the License Server.

Optional:

```bash
BRAND_LOGO_URL=https://example.com/logo.png
BRAND_FAVICON_URL=https://example.com/favicon.png
```

### 5. Check the Stack

```bash
docker compose -f deploy/docker/mail-only/docker-compose.yml ps
docker compose -f deploy/docker/mail-only/docker-compose.yml logs -f --tail=100
```

Local checks:

```bash
curl -I http://127.0.0.1:3000/
curl http://127.0.0.1:3000/api/config
```

Expected:

- frontend returns `200`
- `/api/config` shows `product.edition` as `mail_only`
- for Free Webmail, `/api/config` shows `product.tier` as `free`,
  `license.required` as `false`, and `license.status` as `free`

### 6. Reverse Proxy Example: Nginx

Install Nginx:

```bash
sudo apt-get install -y nginx
```

Example server block:

```nginx
server {
    listen 80;
    server_name mail.example.com;

    client_max_body_size 80m;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
```

Enable TLS with your preferred certificate tool, such as Certbot:

```bash
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d mail.example.com
```

Then test:

```bash
curl -I https://mail.example.com/
curl https://mail.example.com/api/config
```

## Windows Install

Recommended Windows deployment uses Docker Desktop with WSL 2.

### 1. Install Prerequisites

Install:

- Windows 10/11 or Windows Server with virtualization enabled
- WSL 2
- Docker Desktop
- PowerShell 7 or Windows PowerShell

Confirm Docker:

```powershell
docker --version
docker compose version
```

If Docker Desktop asks to enable WSL 2 integration, enable it and restart Docker
Desktop.

### 2. Extract the Bundle

In PowerShell:

```powershell
New-Item -ItemType Directory -Force C:\Zimphony | Out-Null
Expand-Archive .\zimphony-webmail-free-YYYYMMDD-HHMMSS.zip -DestinationPath C:\Zimphony
Set-Location C:\Zimphony\zimphony-webmail-free-YYYYMMDD-HHMMSS
```

### 3. Interactive Install

The packaged installer is a Bash script. Run it through WSL:

```powershell
wsl bash ./install.sh
```

If line endings were changed by a transfer tool, fix them first:

```powershell
wsl sed -i 's/\r$//' ./install.sh ./deploy/docker/install.sh ./deploy/docker/mail-only/install.sh
wsl bash ./install.sh
```

### 4. Non-interactive Install

PowerShell example:

```powershell
wsl env NONINTERACTIVE=1 `
  WEBMAIL_HOST=mail.example.com `
  ZIMBRA_SOAP_URL=https://zimbra.example.com/service/soap `
  ZIMBRA_ADMIN_EMAIL=admin@example.com `
  ZIMBRA_ADMIN_PASSWORD='change-me' `
  BRAND_NAME='Zimphony' `
  BRAND_TAGLINE='Business email that grows with you' `
  ADMIN_HELP_TEXT='Help : support@example.com' `
  bash ./install.sh
```

### 5. Check the Stack

```powershell
docker compose -f .\deploy\docker\mail-only\docker-compose.yml ps
docker compose -f .\deploy\docker\mail-only\docker-compose.yml logs --tail=100
```

Local checks:

```powershell
curl.exe -I http://localhost:3000/
curl.exe http://localhost:3000/api/config
```

Expected:

- frontend returns `200`
- `/api/config` shows `product.edition` as `mail_only`

### 6. Public Access on Windows

For production Windows hosting, put a reverse proxy in front of port `3000`.
Common options:

- IIS with Application Request Routing
- Nginx for Windows
- Caddy
- Cloudflare Tunnel
- an external load balancer

Make sure the public hostname routes all paths to `localhost:3000`, including:

- `/`
- `/admin`
- `/api/*`
- `/_next/*`

## Operations

### Stop

Linux:

```bash
docker compose -f deploy/docker/mail-only/docker-compose.yml down
```

Windows PowerShell:

```powershell
docker compose -f .\deploy\docker\mail-only\docker-compose.yml down
```

### Start

Linux:

```bash
docker compose -f deploy/docker/mail-only/docker-compose.yml up -d
```

Windows PowerShell:

```powershell
docker compose -f .\deploy\docker\mail-only\docker-compose.yml up -d
```

### Restart After Env Changes

Linux:

```bash
docker compose -f deploy/docker/mail-only/docker-compose.yml up -d --build
```

Windows PowerShell:

```powershell
docker compose -f .\deploy\docker\mail-only\docker-compose.yml up -d --build
```

### Logs

Linux:

```bash
docker compose -f deploy/docker/mail-only/docker-compose.yml logs -f --tail=100
```

Windows PowerShell:

```powershell
docker compose -f .\deploy\docker\mail-only\docker-compose.yml logs -f --tail=100
```

### Backup Runtime Data

The important runtime state is in the Compose volume named `backend-data` inside
the package. Docker expands that to a host volume name based on the Compose
project name, so the exact host volume varies by extracted package directory or
`COMPOSE_PROJECT_NAME`.

Find the actual host volume before backing up:

```bash
docker compose -f deploy/docker/mail-only/docker-compose.yml config --volumes
docker volume ls --filter name=backend-data
```

Set `BACKEND_DATA_VOLUME` to the matching host volume name, then back it up.
Linux example:

```bash
BACKEND_DATA_VOLUME=<actual-backend-data-volume>
docker run --rm \
  -v "${BACKEND_DATA_VOLUME}:/data:ro" \
  -v "$PWD":/backup \
  alpine tar czf /backup/zimphony-backend-data.tar.gz -C /data .
```

Windows PowerShell example:

```powershell
$BACKEND_DATA_VOLUME = "<actual-backend-data-volume>"
docker run --rm `
  -v "${BACKEND_DATA_VOLUME}:/data:ro" `
  -v ${PWD}:/backup `
  alpine tar czf /backup/zimphony-backend-data.tar.gz -C /data .
```

Do not paste generated env files, license keys, mailbox passwords, or backup
contents into support tickets. Report only the volume name and command status.

## Post-install Validation

After deployment, validate:

- Open `https://mail.example.com/`
- Login to webmail.
- Open `/admin`.
- Login as postmaster.
- Confirm `/api/config` returns `mail_only`.
- For Free Webmail, confirm `/api/config` shows `product.tier = free`,
  `license.required = false`, and `license.status = free`.
- Confirm branding/logo appears on webmail login, webmail sidebar, admin login,
  and admin sidebar.
- Confirm customer admin help text is visible beside search.
- Using only approved test data, create and delete a test distribution list.
- Using only an approved test mailbox, send a small test email.
- Using only approved test data, upload a small attachment and send.
- Test on mobile or tablet width.

The Free RC smoke described by Zimphony verifies package topology,
license-free configuration, and no-login page rendering. For this RC, founder
real mailbox/API smoke targets Zimbra 10 only. Zimbra 8.8 and Zimbra 9
compatibility are not certified for this RC yet; earlier versions may be
evaluated later or case-by-case before customer use.

## Troubleshooting

### `docker: command not found`

Install Docker Engine on Linux or Docker Desktop on Windows.

### Frontend loads but API fails

Check:

```bash
docker compose -f deploy/docker/mail-only/docker-compose.yml logs backend
docker compose -f deploy/docker/mail-only/docker-compose.yml logs frontend
```

Confirm `INTERNAL_API_UPSTREAM=http://backend:8000` is present in the frontend
service.

### Cannot login

Check:

- `ZIMBRA_SOAP_URL`
- `DEFAULT_ZIMBRA_SOAP_URL`
- `ZIMBRA_ADMIN_EMAIL`
- `ZIMBRA_ADMIN_PASSWORD`
- network access from Docker host to the mail server
- SSL trust if the mail server uses a private certificate

### Branding disappears after restart

Confirm the `backend-data` volume exists and is attached to the backend service.

### Uploads fail through reverse proxy

Set the proxy upload limit high enough. For Nginx:

```nginx
client_max_body_size 80m;
```

### Edition looks wrong

Check:

```bash
curl http://localhost:3000/api/config
```

Expected for this package:

```text
product.edition = mail_only
```
