254 lines
8.6 KiB
Markdown
254 lines
8.6 KiB
Markdown
# Nyone Docker Bundle Deployment
|
|
|
|
This folder is a self-contained deployment bundle. A server only needs the `docker-bundle/` directory; the application repository is cloned later into `runtime/source` by `scripts/deploy.sh`.
|
|
|
|
## What It Runs
|
|
|
|
- Laravel app served by Apache.
|
|
- PostgreSQL database.
|
|
- Redis cache for Laravel cache and streaming viewer counts.
|
|
- MediaMTX for RTMP ingest, HLS playback, hooks, recordings, and the private API.
|
|
- Laravel queue worker.
|
|
- Laravel scheduler worker.
|
|
- Long-running `streaming:sync-viewer-counts` worker.
|
|
|
|
Default ports use the services' normal defaults:
|
|
|
|
- HTTP app proxy: `80`
|
|
- RTMP ingest: `1935`
|
|
- MediaMTX HLS on localhost: `8888`
|
|
- MediaMTX API inside Docker: `9997`
|
|
- PostgreSQL inside Docker: `5432`
|
|
- Redis inside Docker: `6379`
|
|
|
|
HTTP and RTMP are published by default. HLS is published only on `127.0.0.1:8888` by default so host Nginx can proxy `HLS_DOMAIN` directly to MediaMTX without exposing the HLS port publicly.
|
|
|
|
## Server Requirements
|
|
|
|
- Docker with Compose v2.
|
|
- Git.
|
|
- OpenSSL.
|
|
- Zip.
|
|
- Network access from the server to the Git repository.
|
|
- DNS for both `APP_DOMAIN` and `HLS_DOMAIN`.
|
|
- External TLS termination through a reverse proxy, load balancer, or CDN.
|
|
|
|
If a host-level reverse proxy already uses port `80`, set `APP_HTTP_PORT` to another local port for the app and keep `HLS_HTTP_PORT` on a local MediaMTX port for HLS.
|
|
|
|
## First Deployment
|
|
|
|
```bash
|
|
cd docker-bundle
|
|
cp .env.example .env
|
|
nano .env
|
|
chmod +x scripts/deploy.sh
|
|
chmod +x scripts/backup.sh
|
|
./scripts/deploy.sh
|
|
```
|
|
|
|
At minimum, change these values in `.env`:
|
|
|
|
```env
|
|
GIT_REPOSITORY_URL=https://github.com/your-org/nyone.git
|
|
GIT_REF=main
|
|
APP_DOMAIN=nyone.example.com
|
|
HLS_DOMAIN=nyone-hls.example.com
|
|
PUBLIC_SCHEME=https
|
|
```
|
|
|
|
Leave `APP_KEY`, `POSTGRES_PASSWORD`, and `MEDIAMTX_SHARED_SECRET` empty on first deploy if you want `scripts/deploy.sh` to generate them.
|
|
|
|
The deploy script will:
|
|
|
|
1. Create `.env` from `.env.example` if needed.
|
|
2. Generate missing secrets.
|
|
3. Clone or update the app into `runtime/source`.
|
|
4. Validate the Compose config.
|
|
5. Build Docker images.
|
|
6. Start PostgreSQL and Redis.
|
|
7. Run Laravel migrations.
|
|
8. Start Apache, MediaMTX, queue, scheduler, and viewer-sync services.
|
|
|
|
## Runtime Configuration
|
|
|
|
This bundle does not use the cloned repository's `.env` file for production settings. Docker Compose passes configuration from `docker-bundle/.env` into the containers.
|
|
|
|
Important values:
|
|
|
|
- `APP_DOMAIN`: Laravel app hostname.
|
|
- `HLS_DOMAIN`: HLS playback hostname. Must differ from `APP_DOMAIN`.
|
|
- `APP_HTTP_PORT`: host port published by Apache, default `80`.
|
|
- `HLS_HTTP_PORT`: host address published by MediaMTX HLS, default `127.0.0.1:8888`.
|
|
- `RTMP_PUBLIC_PORT`: host RTMP port, default `1935`.
|
|
- `PUBLIC_SCHEME`: usually `https` behind external TLS.
|
|
- `SEED_DATABASE`: defaults to `false`; set to `true` only if you intentionally want the app seeder to run.
|
|
- `QUEUE_NAMES`: queue list for the Laravel database queue worker, default `default`.
|
|
|
|
The MediaMTX config is Docker-owned at `docker/mediamtx/mediamtx.yml.template`; it does not mount or depend on `deploy/mediamtx.yml` from the cloned repository.
|
|
|
|
## Operations
|
|
|
|
Run commands from the `docker-bundle/` directory.
|
|
|
|
```bash
|
|
docker compose --env-file .env -f docker-compose.yml ps
|
|
docker compose --env-file .env -f docker-compose.yml logs -f app
|
|
docker compose --env-file .env -f docker-compose.yml logs -f mediamtx
|
|
docker compose --env-file .env -f docker-compose.yml exec app php artisan migrate:status
|
|
docker compose --env-file .env -f docker-compose.yml exec app php artisan streaming:sync-viewer-counts --once -v
|
|
```
|
|
|
|
Redeploy after new Git changes:
|
|
|
|
```bash
|
|
./scripts/deploy.sh
|
|
```
|
|
|
|
Create a restore-ready backup zip:
|
|
|
|
```bash
|
|
./scripts/backup.sh
|
|
```
|
|
|
|
For the most consistent app storage and recording backup, run it during a maintenance window and let the script stop writer services while it archives files:
|
|
|
|
```bash
|
|
./scripts/backup.sh --stop-writers
|
|
```
|
|
|
|
Backups are written to `backups/` by default. Each zip contains deployment files, `.env`, `runtime/source`, PostgreSQL dump, app storage, Redis data, checksums, restore notes, and a `restore.sh` helper.
|
|
|
|
Restore on a fresh server after installing Docker and Compose:
|
|
|
|
```bash
|
|
unzip nyone-backup-YYYYMMDD-HHMMSS.zip
|
|
cd nyone-backup-YYYYMMDD-HHMMSS
|
|
chmod +x restore.sh
|
|
./restore.sh /opt/nyone/docker-bundle
|
|
```
|
|
|
|
Stop the stack:
|
|
|
|
```bash
|
|
docker compose --env-file .env -f docker-compose.yml down
|
|
```
|
|
|
|
Do not remove volumes unless you intend to delete database, Redis, and stored app media data.
|
|
|
|
## External Proxy Notes
|
|
|
|
Forward `APP_DOMAIN` to the Apache container's published HTTP port. Forward `HLS_DOMAIN` directly to the MediaMTX HLS localhost port. Preserve the `Host` header for the Laravel app and set `X-Forwarded-Proto: https` when TLS is terminated outside Docker.
|
|
|
|
Expose TCP `1935` publicly for OBS/RTMP ingest. Do not expose MediaMTX API port `9997` publicly.
|
|
|
|
## Nginx TLS Proxy Example
|
|
|
|
Use this pattern when Nginx runs on the host and handles public HTTP/HTTPS. In that setup, let Nginx own ports `80` and `443`, and bind the Docker Apache service to a private local port:
|
|
|
|
```env
|
|
APP_HTTP_PORT=127.0.0.1:8080
|
|
HLS_HTTP_PORT=127.0.0.1:8888
|
|
PUBLIC_SCHEME=https
|
|
APP_DOMAIN=app.example.com
|
|
HLS_DOMAIN=hls.example.com
|
|
RTMP_PUBLIC_PORT=1935
|
|
```
|
|
|
|
Run `./scripts/deploy.sh` after changing `.env`, then point Nginx app traffic at `http://127.0.0.1:8080` and HLS traffic at `http://127.0.0.1:8888`. Replace the example domains and certificate paths before enabling the config.
|
|
|
|
```nginx
|
|
# /etc/nginx/sites-available/nyone-docker-bundle.conf
|
|
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name app.example.com hls.example.com;
|
|
|
|
location /.well-known/acme-challenge/ {
|
|
root /var/www/html;
|
|
}
|
|
|
|
location / {
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name app.example.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
|
|
|
|
client_max_body_size 100M;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:8080;
|
|
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-Host $host;
|
|
proxy_set_header X-Forwarded-Port 443;
|
|
proxy_set_header X-Forwarded-Proto https;
|
|
proxy_read_timeout 120s;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name hls.example.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/hls.example.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/hls.example.com/privkey.pem;
|
|
|
|
location / {
|
|
if ($request_method = OPTIONS) {
|
|
return 204;
|
|
}
|
|
|
|
proxy_pass http://127.0.0.1:8888;
|
|
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-Host $host;
|
|
proxy_set_header X-Forwarded-Port 443;
|
|
proxy_set_header X-Forwarded-Proto https;
|
|
proxy_buffering off;
|
|
proxy_request_buffering off;
|
|
proxy_read_timeout 60s;
|
|
proxy_send_timeout 60s;
|
|
add_header Access-Control-Allow-Origin "https://app.example.com" always;
|
|
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
|
|
add_header Access-Control-Allow-Headers "Range, Origin, Accept, Content-Type" always;
|
|
add_header Access-Control-Expose-Headers "Content-Length, Content-Range" always;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate" always;
|
|
}
|
|
}
|
|
```
|
|
|
|
Enable and validate on the server:
|
|
|
|
```bash
|
|
sudo ln -s /etc/nginx/sites-available/nyone-docker-bundle.conf /etc/nginx/sites-enabled/nyone-docker-bundle.conf
|
|
sudo nginx -t
|
|
sudo systemctl reload nginx
|
|
```
|
|
|
|
Keep RTMP separate from this HTTP proxy. Open TCP `1935` to the Docker host so OBS can publish directly to MediaMTX.
|
|
|
|
## HLS CORS Check
|
|
|
|
Host Nginx owns the public HLS CORS headers. MediaMTX is configured with `hlsAllowOrigins: []` so the response does not include duplicate `Access-Control-Allow-Origin` headers after proxying.
|
|
|
|
Check the deployed headers with the exact app origin:
|
|
|
|
```bash
|
|
curl -I -H "Origin: https://app.example.com" "https://hls.example.com/channel/index.m3u8"
|
|
```
|
|
|
|
The response should contain one `Access-Control-Allow-Origin` header matching the app origin. If two `Access-Control-Allow-Origin` headers appear, rebuild the MediaMTX container from this bundle and reload Nginx.
|