# Docker Quick Start Get NewTon DC Tournament Manager running in Docker in under 2 minutes. **Prerequisites:** Docker and Docker Compose installed. Don't have Docker? [Download Docker Desktop](https://www.docker.com/products/docker-desktop/) (macOS/Windows) or install via your package manager (Linux). --- ## Quick Start ```bash mkdir newton-tournament cd newton-tournament curl -O https://raw.githubusercontent.com/skrodahl/NewTon/main/docker/docker-compose.yml docker compose up -d ``` Open `http://localhost:2020` — the Tournament Manager is running. **Port 2020 = "Double 20"** — nginx runs on port 2020 internally. The compose file publishes the same port on the host. > **Camera / QR scanning requires HTTPS.** The quick start runs HTTP only. Pick one of the three setups below to enable camera access. --- ## Setup Options > **Image tag:** All compose files below use `skrodahl/newton:latest` (Docker Hub) with GHCR as a commented alternative. ### SSL (All Platforms) SSL enabled by default — gives you HTTPS and camera access on any platform. ```yaml services: newton-tournament: image: skrodahl/newton:latest # Alternative: ghcr.io/skrodahl/newton:latest container_name: newton ports: - "2020:2020" # HTTP — redirects to HTTPS - "443:443" # HTTPS volumes: - ./tournaments:/var/www/html/tournaments - ./images:/var/www/html/images:ro - newton-ssl:/etc/nginx/ssl # Persists the auto-generated certificate restart: unless-stopped environment: - TZ=Europe/Oslo - SSL_ENABLED=true # Auto-generates a self-signed cert (30-year, SAN) - NEWTON_API_ENABLED=true # Set false to disable tournament upload/download - NEWTON_DEMO_MODE=false # Set true to show a privacy banner # - NEWTON_LANDING_PAGE=true # Show landing page at root URL # - NEWTON_BASE_URL=https://darts.example.com volumes: newton-ssl: ``` Save as `docker-compose.yml` in a folder of your choice, then run `docker compose up -d`. That's it. Access at `https://localhost`. Your browser will warn about the self-signed certificate on first visit — accept the exception once and it will not appear again. #### LAN Access via `.local` Phones, tablets, and scoring stations on the same network can reach the container by name (e.g. `https://newtondarts.local`) once mDNS is set up on the host. macOS has Bonjour built in; Linux needs avahi-daemon; Windows needs Bonjour installed. See [Docs/MDNS.md](Docs/MDNS.md) for full setup steps. --- ### Behind a Reverse Proxy The container joins your proxy's Docker network. SSL termination and HTTPS are handled by the proxy — no `SSL_ENABLED` needed. ```yaml services: newton-tournament: image: skrodahl/newton:latest # Alternative: ghcr.io/skrodahl/newton:latest container_name: newton # ports: # - "2020:2020" # Remove when using a reverse proxy networks: - proxy_network volumes: - ./tournaments:/var/www/html/tournaments - ./images:/var/www/html/images:ro restart: unless-stopped environment: - TZ=Europe/Oslo - NEWTON_API_ENABLED=true - NEWTON_DEMO_MODE=false # - NEWTON_LANDING_PAGE=true # - NEWTON_BASE_URL=https://darts.example.com networks: proxy_network: external: true ``` Save as `docker-compose.yml` in a folder of your choice, then run `docker compose up -d`. That's it. In Nginx Proxy Manager, point the proxy host to `newton:2020`. #### Camera Access Behind NPM NPM sets its own `Permissions-Policy` header that overrides the container's, blocking camera access in the Chalker. Add this to the **Advanced** tab of your NPM proxy host: ```nginx more_set_headers "Permissions-Policy: geolocation=(), microphone=(), camera=(self), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=(), ambient-light-sensor=()"; ``` #### Demo / Public Site ```yaml environment: - NEWTON_API_ENABLED=false # Disable upload API for security - NEWTON_DEMO_MODE=true # Show privacy banner - NEWTON_LANDING_PAGE=true # Show landing page at root ``` #### Analytics Instance A read-only analytical surface — browse leaderboards, match history, and brackets. Tournament data comes from shared tournament files on disk. No tournament management tabs, no deletion. Accepts tournament uploads from venue instances via the relay API. ```yaml environment: - NEWTON_API_ENABLED=true # Required for auto-import and bracket view - NEWTON_MODE=analytics # Analytics-only mode ``` #### Maintaining an analytics-only instance An analytics-only instance hides tournament management, including the register import/export tools. To do maintenance on it — correcting a finished tournament, restoring a register backup — append `?tm` to the URL, which loads the full Tournament Manager for that browser: ``` https://your-analytics-host/tournament.php?tm ``` If the instance is shared with club members, set a password so `?tm` alone is not enough: ```yaml environment: - NEWTON_TM_PASSWORD=choose-something ``` The URL then becomes `?tm=choose-something`. A missing or wrong password simply loads the ordinary Analytics page, with no error and no hint that a maintenance mode exists. This is a shared password in an environment variable, not user accounts. It stops a curious club member; it is not protection against someone determined, and the password appears in the server's access log and your browser history. **Protecting uploads with an API key.** Set `NEWTON_API_KEY` on the analytics instance, and members can open it without a login while only the club can write to it: ```yaml environment: - NEWTON_MODE=analytics - NEWTON_TM_PASSWORD=choose-something - NEWTON_API_KEY=a-long-random-string ``` - Everything that changes the server needs the key: uploading or deleting a tournament, saving corrections, and the relay. Reading never does, so Analytics works for everyone. - On the venue instance, enter the same key in **Global Settings → Server & backup → Remote backup → API key**. Backups then carry it. - `?tm` works as before: with the correct password, the page is given the key, so corrections and deletes go through. This needs `NEWTON_TM_PASSWORD`; without a password, `?tm` never receives the key. - Network handover (Chalker lanes) is switched off on an analytics-only instance. Set the key on the instance that receives backups. On a venue instance that runs tournaments, its own Backup to server and Delete would then need `?tm`. **Correcting achievements.** Under `?tm`, each tournament in Analytics → Register → Tournaments has an **Edit** button. Pick a player and add or remove 180s, tons, lollipops, high outs and short legs. Corrections are saved on the server in `tournaments/corrections/corrections.json` and apply to Analytics on every device the next time it loads. The tournament itself and each browser's register are left as recorded, so re-uploading or re-importing a tournament keeps its corrections. Mount the `./tournaments` and `./images` volumes. Configure the venue instance to auto-backup to this server (Remote backup in Global Settings, with the API key if you set one), or share the same tournaments directory. --- ## Environment Variables | Variable | Default | Description | |---|---|---| | `SSL_ENABLED` | `false` | Auto-generates a self-signed certificate; enables HTTPS on `HTTPS_PORT` and redirects HTTP on `HTTP_PORT` | | `HTTP_PORT` | `2020` | HTTP listening port (redirects to HTTPS when SSL enabled) | | `HTTPS_PORT` | `443` | HTTPS listening port | | `SSL_HOSTNAME` | `newtondarts` | Hostname used in the auto-generated SSL certificate's Subject Alternative Name. Set this to match the `.local` hostname you advertise via mDNS — see [Docs/MDNS.md](Docs/MDNS.md). Falls back to deprecated `MDNS_HOSTNAME` if unset. | | `NEWTON_API_ENABLED` | `true` | Enables REST API endpoints for tournament upload, download, and delete. `false`, `0`, `off`, and `no` (any case) all disable | | `NEWTON_RELAY_ALLOWLIST` | *(unset)* | Comma-separated hostnames the relay API (`api/relay.php`) may forward to, e.g. `results.myclub.org`. Unset = any host | | `NEWTON_MODE` | `full` | App mode. `analytics` hides tournament management tabs, shows only Analytics and limited Global Settings | | `NEWTON_TM_PASSWORD` | *(unset)* | Password for the `?tm` maintenance URL (see below). Unset = no password required | | `NEWTON_API_KEY` | *(unset)* | Key required (as the `X-API-Key` header) for uploads, deletes, corrections and the relay. Reading never needs it. Unset = no key required | | `NEWTON_DEMO_MODE` | `false` | Shows a privacy banner at the top of the app | | `NEWTON_LANDING_PAGE` | `false` | Shows a landing page at the root URL instead of loading the app directly | | `NEWTON_BASE_URL` | *(unset)* | Canonical URL for Open Graph and Twitter Card meta tags on the landing page | | `NEWTON_GITHUB_URL` | `https://github.com/skrodahl/NewTon` | GitHub link shown in the demo banner | --- ## Security ### REST API **Without `NEWTON_API_KEY`, the REST API has no authentication.** Do not expose the container directly to the public internet without additional protection. Safe options: - ✅ **Local network only** — home or club WiFi, default setup is fine - ✅ **Localhost only** — by default, Docker binds to `0.0.0.0`, meaning the container is reachable from any device on your network. To restrict access to the local machine only, specify the loopback address explicitly in your `ports` mapping: ```yaml ports: - "127.0.0.1:2020:2020" ``` With this in place, `http://localhost:2020` still works, but other devices on the network cannot reach the container. - ✅ **Behind VPN** — Tailscale, WireGuard, or similar - ✅ **API key for writes** — set `NEWTON_API_KEY`: uploads, deletes, corrections and the relay need it; reading stays open. The way to run a public analytics-only instance (see above) - ✅ **Reverse proxy with auth** — nginx or Caddy with HTTP basic authentication (members then need the login too) - ✅ **Disable API** — set `NEWTON_API_ENABLED=false` ### Security Headers The Docker image includes comprehensive security headers enabled by default: - **X-Frame-Options: SAMEORIGIN** — Prevents clickjacking - **X-Content-Type-Options: nosniff** — Prevents MIME-type sniffing - **Referrer-Policy: strict-origin-when-cross-origin** — Limits referrer data leakage - **Permissions-Policy** — Restricts camera and other device access to the app origin - **Content-Security-Policy** — Prevents loading external resources - **Server tokens hidden** — nginx and PHP versions not disclosed NewTon DC Tournament Manager achieves an **A grade** on [securityheaders.com](https://securityheaders.com) out of the box. ### HSTS Strict-Transport-Security is included automatically when `SSL_ENABLED=true`. It is not set in HTTP-only mode to avoid breaking non-SSL deployments. When terminating HTTPS at a reverse proxy, add HSTS there instead: ```nginx # Nginx Proxy Manager — Advanced tab add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; ``` --- ## Customization ### Logo and Payment QR Code Place your own files in the `images/` folder. The volume is mounted read-only from the host, so changes are picked up immediately — no container restart needed. ```bash mkdir -p images # Custom logo — supports .png, .jpg, .jpeg, or .svg cp /path/to/your/logo.jpg ./images/logo.jpg # Custom payment QR code cp /path/to/your/payment-qr.png ./images/payment.png ``` --- ## Troubleshooting ### Port Already in Use Change the host port (the first number) in `docker-compose.yml`, then open `http://localhost:9000`: ```yaml ports: - "9000:2020" ``` ### Container Won't Start ```bash docker compose logs ``` ### Can't Access http://localhost:2020 1. Verify the container is running: `docker compose ps` — should show `0.0.0.0:2020->2020/tcp` 2. Try `http://127.0.0.1:2020` instead of `localhost` 3. Check logs: `docker compose logs` ### Browser Warning on https://localhost Expected with a self-signed certificate. Accept the exception once — it will not appear again. The cert is stored in the `newton-ssl` named volume and persists across restarts. ### `.local` Not Reachable mDNS resolution is a host-side concern, not handled by the container. See [Docs/MDNS.md](Docs/MDNS.md) for full setup. Quick checks: - macOS: Bonjour is built in — should just work - Windows: install Bonjour - Linux: install and start `avahi-daemon` on the host, set hostname, and configure `allow-interfaces` to exclude Docker bridge interfaces - Confirm resolution from another device on the LAN: `ping newtondarts.local` --- ## Need Help? - **Application help:** Press F1 inside the tournament manager - **REST API reference:** [rest-api.html](rest-api.html) - **Privacy model:** [privacy.html](privacy.html) - **Issues & feedback:** [GitHub Issues](https://github.com/skrodahl/NewTon/issues)