OOTT is available on Docker Hub as a pre-built image [here](https://hub.docker.com/repository/docker/rzuasti/oott/general).
To use it I recommend using docker compose, you can find a sample compose file [here](https://github.com/rzuasti/oott/blob/main/examples/docker-compose.yml).
If you use our Docker Hub image and docker compose the steps you have to follow are:
1. Create a folder structure in your host
2. Create the configuration file
3. Create the `docker-compose.yml` file
4. Start the service
#### 1. Create a folder structure in your host
You need a place to store the docker-compose.yml file, the OOTT configuration file and the database that will store the application state:
```bash
mkdir -p /docker/oott/config
mkdir -p /docker/oott/db
```
You can choose whatever structure or locations you choose. Note that:
* The user that runs the docker process must have read access to the config folder and read/write access to the db folder
* OOTT uses SQLite and it doesn't really like remote access so the `db/` folder should be local to your docker host
#### 2. Create the configuration file
Create the `oott.toml` file (for example at `/docker/oott/config/oott.toml` in your docker host) and populate it with your preferences. You can use the [provided example](https://github.com/rzuasti/oott/blob/main/examples/sample_oott.toml) as baseline.
> [!IMPORTANT]
> The `database.path` option must always be set to `"/db/oott.db"` when using our docker image.
#### 3. Create the `docker-compose.yml` file
Create a docker compose file to run the container (for example at `/docker/oott/docker-compose.yml`), you can use the [provided example](https://github.com/rzuasti/oott/blob/main/examples/docker-compose.yml) as is or adjust it to match your environment.
#### 4. Start the service
Run `docker compose up -d` from where you placed your `docker-compose.yml` file and verify everything is running smoothly.
You can check the applications log using `docker logs CONTAINER_ID -f`, to see the active containers you can use `docker ps`.
OOTT comes with a pre-built NixOS flake that you can integrate in your configuration. If you are not using flakes, well you should. If you still won't I guess you can use the [flake code](https://github.com/rzuasti/oott/tree/main/nix) as a baseline and write your own derivation.
To integrate the OOTT flake into your config in most cases you should do the following:
#### 3. Enable and setup OOTT in your system configuration
Finally, in your `configuration.nix` (or in an import file) enable and configure OOTT (note that you should embed the following sections in your file appropriately):
If you are using Docker I recommend writing the config using TOML, [here](https://github.com/rzuasti/oott/blob/main/examples/sample_oott.toml) is a sample with all the supported options.
|`networking.interface`|`eno1`|Network interface to use for scans. Optional — if not set, the first non-loopback connected interface is used automatically.|
|`arp_scanner.enabled`|`true`|Whether to run the ARP scanner. Defaults to enabled; set to `false` to turn it off.|
|`arp_scanner.wait_between_scans`|`15m`|Time to wait between each network scan (you can express it in seconds, minutes, hours, etc. as a suffix - for example: 30s, 10m, 1h)|
|`arp_scanner.sender_timeout`|`1m`|If the ARP sender process takes longer than this it will be stopped (for a class C network - 254 IPs - it should take less than a minute)|
|`arp_scanner.scan_duration`|`10m`|How long to wait for response packets on each scan (5m to 10m is a good timeframe for a class B or C network)|
|`mdns_scanner.enabled`|`true`|Whether to run the mDNS/Bonjour scanner. Defaults to enabled; set to `false` to turn it off.|
|`mdns_scanner.probe_timeout`|`2s`|When an mDNS-discovered IP is not in the OS ARP cache, how long to wait for a targeted ARP probe reply to resolve its MAC address|
|`ssdp_scanner.enabled`|`true`|Whether to run the SSDP/UPnP scanner. Defaults to enabled; set to `false` to turn it off.|
|`ssdp_scanner.probe_timeout`|`2s`|When an SSDP/UPnP-discovered IP is not in the OS ARP cache, how long to wait for a targeted ARP probe reply to resolve its MAC address|
|`dhcp_scanner.enabled`|`true`|Whether to run the DHCP scanner. Defaults to enabled; set to `false` to turn it off.|
|`notifications.method`|`pushover`|For now just pushover, you can set this to "none" to avoid sending notifications (it will just log)|
|`notifications.notify_when_not_seen_for`|`1w`|Send a notification if a device comes back online after not being seen for this timeframe (you can use hours, weeks, etc.)|
|`notifications.pushover.token`||Your pushover token goes here, just copy&paste from their website after creating the app|
|`notifications.pushover.user_key`||User key goes here, this is the account wide code for pushover|
|`retention.window`|`365d`|How long to retain device events and notifications. Records older than this are purged daily. Accepts duration strings (e.g. `90d`, `1y`, `6m`). Defaults to one year.|
> The mDNS (UDP `5353`), SSDP (UDP `1900`) and DHCP (UDP `67`) ports are fixed by their respective protocols and **cannot be changed**. They must be available on the server where OOTT is installed.
> OOTT binds these sockets with address/port reuse, so it can run alongside other responders already listening on them (for example `avahi` on `5353`, `minidlna` on `1900`, or a DHCP server/relay on `67`). However, the ports must not be blocked by a host firewall, and the corresponding multicast/broadcast traffic must be allowed to reach the host — otherwise the scanners will not discover any devices.
> Port `67` is a privileged port, so OOTT must run with sufficient privileges to bind it (it already requires raw-socket access for the ARP scanner).
>
> When running under Docker these scanners require host networking (or an equivalent setup that exposes the host's multicast and broadcast traffic to the container); see the [sample compose file](https://github.com/rzuasti/oott/blob/main/examples/docker-compose.yml).
OOTT stores a timestamped event in the database for every device detected on every scan. Storage therefore scales with three factors: number of active devices, scan frequency, and the retention window.
The figures below assume a 1-minute scan duration (`arp_scanner.scan_duration = 1m`) with a 1-minute wait between scans (`arp_scanner.wait_between_scans = 1m`), giving 720 scans per day, and a 365-day retention window. Assume all devices are continuously online (worst case).
A 15-minute wait (the default) cuts storage to about one eighth of the worst-case figures above — the medium office drops from up to 18 GB to roughly 2 GB per year.
**Retention window** — `retention.window` sets how far back history is kept. Halving the window halves the storage. Useful reference points:
| `retention.window` | Use case |
|---|---|
| `30d` | Minimal footprint, recent activity only |