> Source: https://docs.nometa.az/v4.8.0/deployment

# Deployment

SAMURAI Networks is distributed as a single hardened Docker image (`beyrak44/samurai`). It
bundles the Go backend, the React UI behind Nginx, and an embedded MongoDB.

## Prerequisites

A host with **Docker Engine 20.10+** (or Docker Desktop) and roughly 2 GB of free
RAM. SAMURAI Networks ships as a single container, so Docker is the only dependency.

On most Linux hosts:

```bash
curl -fsSL https://get.docker.com | sh
```

See Docker's [official install guide](https://docs.docker.com/engine/install/) for
Windows, macOS, or distribution-specific steps, and verify with `docker --version`.

## Run

Deploy with the Docker CLI or Docker Compose. Both publish ports 80/443; for production,
set stable secrets (see [Environment](#environment)) and persist the database in a volume.

<Tabs.Tab>

A minimal run, suitable for evaluation:

```bash
docker run -d --name samurai -p 80:80 -p 443:443 beyrak44/samurai:latest
```

To keep the database when the container is removed, add a volume:
`-v samurai-data:/app/mongo-data`.

For production, generate stable secrets once, store them somewhere safe, and pass the same
values on every run:

```bash
JWT_SECRET_KEY=$(openssl rand -hex 32)
DEVICE_ENCRYPTION_KEY=$(openssl rand -hex 32)

docker run -d --name samurai -p 80:80 -p 443:443 \
  -v samurai-data:/app/mongo-data \
  -e JWT_SECRET_KEY="$JWT_SECRET_KEY" \
  -e DEVICE_ENCRYPTION_KEY="$DEVICE_ENCRYPTION_KEY" \
  beyrak44/samurai:latest
```

</Tabs.Tab>

<Tabs.Tab>

Save as `compose.yml`, set the two secrets to stable values (generate each with
`openssl rand -hex 32`), then run `docker compose up -d`:

```yaml
# compose.yml
services:
  samurai:
    image: beyrak44/samurai:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - samurai-data:/app/mongo-data
    environment:
      JWT_SECRET_KEY: "REPLACE_WITH_STABLE_SECRET"
      DEVICE_ENCRYPTION_KEY: "REPLACE_WITH_STABLE_64_HEX_KEY"
    restart: unless-stopped
volumes:
  samurai-data:
```

```bash
docker compose up -d
```

For a quick evaluation, omit the `environment:` block and SAMURAI Networks starts with defaults.

</Tabs.Tab>

## Database: external-first, embedded-fallback

The entrypoint decides at runtime:

- `MONGODB_URL` set and reachable → uses your external MongoDB.
- `MONGODB_URL` set but unreachable → falls back to the embedded `mongod`.
- `MONGODB_URL` unset → uses the embedded `mongod`.

Mount a volume at `/app/mongo-data` to persist the embedded database. For a production
high-availability database, see [Database Redundancy](https://docs.nometa.az/md/v4.8.0/database-redundancy.md).

## Environment

Bootstrap configuration is provided via environment variables; most runtime
settings (sync schedule, CORS, branding, timezone) are managed in **Settings** and
hot-reloaded.

| Variable | Purpose |
| --- | --- |
| `JWT_SECRET_KEY` | Signs session tokens. Auto-generated and stored in the database when unset. |
| `DEVICE_ENCRYPTION_KEY` | 64-hex AES-256 key for stored credentials. Auto-generated on first boot and persisted to `/app/secrets/device_encryption_key` inside the container when unset; an env value always wins. |
| `MONGODB_URL` | External MongoDB (optional; the embedded `mongod` is used if unset) |
| `MONGODB_DATABASE` | Database name (optional) |

> **Warning:** Since v4.6.1 both secrets are generated automatically when omitted, so the minimal
> command is safe to start with. For production, still pin explicit stable values (the
> production command above), or mount a volume at `/app/secrets` so the generated
> `DEVICE_ENCRYPTION_KEY` survives container recreation. **Back the key up**: if it is
> lost or later changes, previously stored device credentials can no longer be decrypted;
> if `JWT_SECRET_KEY` changes, existing sessions are invalidated.

> **Note:** Device passwords, SMTP/LDAP secrets, and tokens are encrypted at rest with
> AES-256-GCM. TLS verification is handled per connector.

## Licensing

SAMURAI Networks is license-gated. [Get a free trial license](https://share.exploit.az/go/free-trial-lic),
then upload your license file in [**Settings → License**](https://docs.nometa.az/md/v4.8.0/administration/settings.md),
where you can review the active license: customer, validity, per-type device limits and
current usage. A running deployment also needs outbound network access to validate the
license, covered next.

## License verification

SAMURAI Networks validates its license at startup and periodically while running. This needs
**outbound** network access from the SAMURAI Networks server for two things: **trusted time** (to
verify the license has not expired) and the **server's public IP** (to enforce IP-locked
licenses). All licensing traffic is outbound, so no inbound firewall rules are needed.

### Required outbound access

| Purpose | Host | Protocol | Port |
| --- | --- | --- | --- |
| Trusted time (primary) | `pool.ntp.org` | UDP (NTP) | 123 |
| Trusted time (fallback) | `www.google.com` | TCP (HTTPS) | 443 |
| Trusted time (fallback) | `cloudflare.com` | TCP (HTTPS) | 443 |
| Public IP (consensus) | `checkip.amazonaws.com` | TCP (HTTPS) | 443 |
| Public IP (consensus) | `api.ipify.org` | TCP (HTTPS) | 443 |
| Public IP (consensus) | `ifconfig.me` | TCP (HTTPS) | 443 |
| DNS resolution | your resolver | UDP/TCP | 53 |

### What must succeed

| Check | Rule |
| --- | --- |
| Trusted time | Either UDP 123 to `pool.ntp.org`, or TCP 443 to Google or Cloudflare. One source is enough. |
| Public IP | At least 2 of the 3 HTTPS sources must be reachable and agree. |
| Both fail | A 6-hour grace window applies; after that the API returns `403 LICENSE_REQUIRED` until access is restored. |

### Notes

- **Allowlist by domain or by port, not by IP.** `pool.ntp.org` is a rotating DNS pool
  and the IP-lookup services sit behind CDNs, so destination IPs are not stable. Permit
  the hostnames (egress proxy) or the ports (UDP 123, TCP 443) outbound, and keep DNS
  (UDP/TCP 53) open.
- These endpoints are fixed; there is currently no setting to use an internal NTP server
  or route through a proxy. The [Outbound Proxy](https://docs.nometa.az/md/v4.8.0/administration/outbound-proxy.md)
  covers device and notification traffic but does **not** apply to these licensing
  endpoints. Air-gapped or restricted-egress sites that cannot allow this
  outbound access will reach the 6-hour limit. If you need internal NTP or offline
  licensing, [contact us](mailto:info@exploit.az).
- The license is locked to one or more specific public IPs (exact match). The public IP
  discovered above is what each match is compared against.
