Files
Meghdad b3bb160b90
All checks were successful
tests / ci (8.5) (push) Successful in 4m0s
linter / quality (push) Successful in 1m16s
tests / ci (8.4) (push) Successful in 1m36s
Splitting MediaRTX from Apache in docker-bundle
2026-05-20 00:59:19 +03:30
..
2026-05-19 20:25:15 +03:30

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

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:

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.

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:

./scripts/deploy.sh

Create a restore-ready backup zip:

./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:

./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:

unzip nyone-backup-YYYYMMDD-HHMMSS.zip
cd nyone-backup-YYYYMMDD-HHMMSS
chmod +x restore.sh
./restore.sh /opt/nyone/docker-bundle

Stop the stack:

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:

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.

# /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:

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:

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.