Refactor load testing scripts to support Bash and PowerShell
- Updated README.md to reflect changes in load testing commands and added usage examples for Bash scripts. - Converted PowerShell load testing scripts to Bash equivalents for better cross-platform compatibility. - Added new Bash scripts: run-loadtests.sh, summarize-artillery.sh, and summarize-k6.sh to mirror existing PowerShell functionality. - Updated admin-api.js and ws-chat.yml for consistency and improved performance metrics reporting. - Enhanced error handling and output formatting in summary scripts for better readability.
This commit is contained in:
1 parent
9f60087a4c
commit
33b6296c5e
82 files changed
+19682
-21133
No files matched your search
+72
-194
@@ -1,64 +1,26 @@
|
||||
# Deployment Guide
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Deployment Guide](#deployment-guide)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Environments](#environments)
|
||||
- [Production Target (Hetzner)](#production-target-hetzner)
|
||||
- [Planned Infrastructure](#planned-infrastructure)
|
||||
- [Cutover Plan](#cutover-plan)
|
||||
- [Production Preflight](#production-preflight)
|
||||
- [Firewall / Security Group](#firewall--security-group)
|
||||
- [Pipeline Readiness](#pipeline-readiness)
|
||||
- [Target Topology](#target-topology)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Environment File](#environment-file)
|
||||
- [Gitea Secrets](#gitea-secrets)
|
||||
- [Pull-and-Run Deployment](#pull-and-run-deployment)
|
||||
- [First-Time or Recovery Provisioning](#first-time-or-recovery-provisioning)
|
||||
- [Health Verification](#health-verification)
|
||||
- [Zero/Low-Downtime Update Pattern](#zerolow-downtime-update-pattern)
|
||||
- [Rollback](#rollback)
|
||||
- [Operational Tasks](#operational-tasks)
|
||||
- [View logs](#view-logs)
|
||||
- [Restart one service](#restart-one-service)
|
||||
- [Stop stack](#stop-stack)
|
||||
- [Keep volumes (recommended) vs remove volumes (destructive)](#keep-volumes-recommended-vs-remove-volumes-destructive)
|
||||
- [Nginx and Proxy Considerations](#nginx-and-proxy-considerations)
|
||||
- [Security Baseline](#security-baseline)
|
||||
- [Common Deployment Pitfalls](#common-deployment-pitfalls)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Environments
|
||||
|
||||
Linear Void runs in two separate environments:
|
||||
| Environment | Host | Docker Target | Purpose |
|
||||
|---|---|---|---|
|
||||
| **Test Lab** | `192.168.88.40` (internal) | `tcp://192.168.88.40:2375` | Integration testing, staging, CI/CD validation |
|
||||
| **Production** | Hetzner VPS (TBD) | `tcp://<hetzner-ip>:2375` | Public linearvoid.tv |
|
||||
|
||||
| Environment | Host | Docker Target | Purpose |
|
||||
| -------------- | -------------------------- | -------------------------- | ---------------------------------------------- |
|
||||
| **Test Lab** | `192.168.88.40` (internal) | `tcp://192.168.88.40:2375` | Integration testing, staging, CI/CD validation |
|
||||
| **Production** | Hetzner VPS (TBD) | `tcp://<hetzner-ip>:2375` | Public linearvoid.tv serving real viewers |
|
||||
|
||||
**Current state (2026-08-06):** Test lab is active and receives automated CD deploys. Production Hetzner VPS is **not yet provisioned**. The CD workflow (`cd.yml`) and `docker-compose.prod.yml` currently target the test lab. Switching to production requires only changing `DOCKER_HOST` — see [Production Deployment](#production-target-hetzner) below.
|
||||
**Current state (2026-08-06):** Test lab is active and receives automated CD deploys. Production Hetzner VPS is **not yet provisioned**. `cd.yml` and `docker-compose.prod.yml` target the test lab; switching to production only requires changing `DOCKER_HOST`.
|
||||
|
||||
## Production Target (Hetzner)
|
||||
|
||||
**Status: Not provisioned.** The production deployment plan is documented and the pipeline is ready — only the VPS needs to be created.
|
||||
**Status: Not provisioned.** Plan documented and pipeline ready; only the VPS needs creation.
|
||||
|
||||
### Planned Infrastructure
|
||||
|
||||
| Resource | Spec |
|
||||
| -------------- | ------------------------------------------------ |
|
||||
| Provider | Hetzner Cloud |
|
||||
| Region | Ashburn, VA (us-east) |
|
||||
| Instance | CX22 (2 vCPU, 4 GB RAM, 40 GB SSD) or equivalent |
|
||||
| OS | Ubuntu 24.04 LTS or Debian 12 |
|
||||
| Bandwidth | 20 TB included traffic/month |
|
||||
| Estimated cost | ~€7–10/month base |
|
||||
- Provider: Hetzner Cloud, Region: Ashburn, VA (us-east)
|
||||
- Instance: CX22 (2 vCPU, 4 GB RAM, 40 GB SSD) or equivalent
|
||||
- OS: Ubuntu 24.04 LTS or Debian 12
|
||||
- Bandwidth: 20 TB included traffic/month; Estimated cost: ~€7–10/month base
|
||||
|
||||
### Cutover Plan
|
||||
|
||||
1. Provision Hetzner VPS (see [Production Deployment](#production-target-hetzner) section)
|
||||
1. Provision Hetzner VPS
|
||||
2. Add production secrets to Gitea (`CI_PROD_*` prefix)
|
||||
3. Update `DOCKER_HOST` in CD workflow to Hetzner public IP
|
||||
4. Trigger deploy → validate health checks
|
||||
@@ -66,44 +28,33 @@ Linear Void runs in two separate environments:
|
||||
6. Enable HTTPS, WAF, cache rules on production zone
|
||||
7. Decommission test lab as public target
|
||||
|
||||
### Production Preflight
|
||||
|
||||
Before the cutover in the plan above, treat the following as the minimum readiness gate:
|
||||
|
||||
1. Verify the Hetzner host is reachable over SSH and Docker remote API TLS is working for the Gitea runner.
|
||||
2. Confirm all required production secrets are present under the `CI_PROD_*` prefix.
|
||||
3. Confirm `linearvoid.tv`, `www.linearvoid.tv`, and `media.linearvoid.tv` DNS targets are correct for the intended phase.
|
||||
4. Complete the manual Cloudflare/UptimeRobot checklist and attach evidence in [Cloudflare Uptimerobot checklist](../docs/operations/cloudflare-uptimerobot-checklist.md).
|
||||
5. Run the production smoke path end to end: viewer SPA, HLS playback, chat connect, admin login, upload, playlist save, and playout health.
|
||||
6. Verify rollback is still possible before the final DNS switch.
|
||||
|
||||
Readiness is not complete until the checklist above and the manual evidence tracker are both satisfied.
|
||||
### Production Preflight (minimum readiness gate)
|
||||
1. Verify Hetzner host reachable over SSH and Docker remote API TLS works for the Gitea runner
|
||||
2. Confirm all production secrets exist under `CI_PROD_*` prefix
|
||||
3. Confirm `linearvoid.tv`, `www.linearvoid.tv`, `media.linearvoid.tv` DNS targets match intended phase
|
||||
4. Complete manual Cloudflare/UptimeRobot checklist (see `docs/operations/cloudflare-uptimerobot-checklist.md`)
|
||||
5. Run production smoke path end-to-end: viewer SPA, HLS playback, chat connect, admin login, upload, playlist save, playout health
|
||||
6. Verify rollback possible before final DNS switch
|
||||
|
||||
### Firewall / Security Group
|
||||
|
||||
| Port | Source | Purpose |
|
||||
| ---- | --------------------- | ------------------------------ |
|
||||
| 22 | Admin IPs | SSH management |
|
||||
| 80 | 0.0.0.0/0 | HTTP (redirect to HTTPS) |
|
||||
| 443 | 0.0.0.0/0 | HTTPS (nginx) |
|
||||
| 2375 | Gitea runner IP only | Docker remote API (TLS) |
|
||||
| Port | Source | Purpose |
|
||||
|---|---|---|
|
||||
| 22 | Admin IPs | SSH management |
|
||||
| 80 | 0.0.0.0/0 | HTTP (redirect to HTTPS) |
|
||||
| 443 | 0.0.0.0/0 | HTTPS (nginx) |
|
||||
| 2375 | Gitea runner IP only | Docker remote API (TLS) |
|
||||
| 9090 | Admin IPs or internal | Prometheus (or tunnel via SSH) |
|
||||
| 3001 | Admin IPs or internal | Grafana (or tunnel via SSH) |
|
||||
| 3001 | Admin IPs or internal | Grafana (or tunnel via SSH) |
|
||||
|
||||
### Pipeline Readiness
|
||||
| Workflow | Trigger | Purpose |
|
||||
|---|---|---|
|
||||
| `ci.yml` | Push to `main` | Lint, build frontend, vet Go, build & push images (`:latest`, `:main`) |
|
||||
| `cd.yml` | Push to `main` | Deploy to test lab (`192.168.88.40:2375`) — runs **in parallel** with CI |
|
||||
| `release.yml` | Tag push `*.*.*` | Build & push versioned images (`:X.Y.Z`, `:X.Y`, `:latest`), create Gitea Release |
|
||||
| `workers-deploy.yml` | After CI succeeds | Deploy frontend to Cloudflare Workers |
|
||||
|
||||
Three Gitea Actions workflows drive the full pipeline:
|
||||
|
||||
| Workflow | Trigger | Purpose |
|
||||
| -------------------- | ----------------- | ----------------------------------------------------------------------------------------- |
|
||||
| `ci.yml` | Push to `main` | Lint, build frontend, vet Go, build & push Docker images (`:latest`, `:main`) |
|
||||
| `cd.yml` | Push to `main` | Deploy to test lab (`192.168.88.40:2375`) — runs **in parallel** with CI |
|
||||
| `release.yml` | Tag push `*.*.*` | Build & push versioned Docker images (`:X.Y.Z`, `:X.Y`, `:latest`), create Gitea Release |
|
||||
| `workers-deploy.yml` | After CI succeeds | Deploy frontend to Cloudflare Workers (see `docs/operations/gitea-cloudflare-workers.md`) |
|
||||
|
||||
CI and CD run simultaneously on every push to `main`. The deploy pulls whatever `:latest` images are available in the registry at that moment.
|
||||
|
||||
The CD workflow (`cd.yml`) uses `DOCKER_HOST` to target the deployment host. To switch targets:
|
||||
CI and CD run simultaneously on every push to `main`. Deploy pulls whatever `:latest` images are available at that moment. `cd.yml` uses `DOCKER_HOST` to target the host:
|
||||
|
||||
```bash
|
||||
# Test lab (current)
|
||||
@@ -113,10 +64,9 @@ DOCKER_HOST=tcp://192.168.88.40:2375
|
||||
DOCKER_HOST=tcp://<hetzner-public-ip>:2375
|
||||
```
|
||||
|
||||
No code changes needed in the pipeline. See `DEPLOYMENT.md` Production Deployment section for full checklist.
|
||||
No pipeline code changes needed to switch targets.
|
||||
|
||||
## Target Topology
|
||||
|
||||
- Host: Linux server with Docker Engine
|
||||
- Reverse proxy: nginx container (linearvoid-nginx)
|
||||
- Core services: playout, chat, redis, postgres
|
||||
@@ -124,16 +74,14 @@ No code changes needed in the pipeline. See `DEPLOYMENT.md` Production Deploymen
|
||||
- CDN/edge: Cloudflare
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker Engine + Docker Compose plugin
|
||||
- Network access to git.allucanget.biz
|
||||
- A valid .env file with production values
|
||||
- DNS configured for linearvoid.tv (and media.linearvoid.tv if split media domain is used)
|
||||
- Valid `.env` file with production values
|
||||
- DNS configured for `linearvoid.tv` (and `media.linearvoid.tv` if split media domain used)
|
||||
|
||||
## Environment File
|
||||
|
||||
Use `.env` with at least:
|
||||
|
||||
`.env` requires at minimum:
|
||||
- `DATABASE_URL` — PostgreSQL connection string
|
||||
- `REDIS_URL` — Redis connection string
|
||||
- `ADMIN_USERNAME` / `ADMIN_PASSWORD` — admin dashboard credentials
|
||||
@@ -141,186 +89,116 @@ Use `.env` with at least:
|
||||
- `VITE_PUBLIC_APP_URL` / `VITE_PUBLIC_WS_URL` / `VITE_PUBLIC_HLS_URL` — frontend URLs
|
||||
|
||||
**Cloudflare R2 (required for HLS segment sync to edge):**
|
||||
|
||||
- `R2_ACCESS_KEY_ID` / `R2_SECRET_ACCESS_KEY` — R2 write credentials
|
||||
- `R2_BUCKET_NAME` — bucket name (e.g. `linearvoid-media`)
|
||||
- `R2_ACCESS_KEY_ID` / `R2_SECRET_ACCESS_KEY`
|
||||
- `R2_BUCKET_NAME` (e.g. `linearvoid-media`)
|
||||
- `R2_ENDPOINT` — S3-compatible endpoint URL
|
||||
- `R2_REGION` — set to `auto`
|
||||
|
||||
**Cloudflare (optional, for Workers):**
|
||||
|
||||
- `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID`
|
||||
- `MEDIA_DOMAIN` — split media domain (e.g. `media.linearvoid.tv`)
|
||||
|
||||
See `.env.example` and `SETUP.md` for full variable reference.
|
||||
|
||||
### Gitea Secrets
|
||||
`cd.yml` injects sensitive values from Gitea secrets into `.env` at deploy time. Configure in the Gitea repository:
|
||||
|
||||
The CD workflow (`cd.yml`) injects sensitive values from Gitea secrets into the `.env` file at deploy time. The following secrets must be configured in the Gitea repository:
|
||||
|
||||
| Secret Name | Description |
|
||||
| ---------------------------- | --------------------------------- |
|
||||
| `DATABASE_URL` | PostgreSQL connection string |
|
||||
| `ADMIN_USERNAME` | Admin dashboard username |
|
||||
| `ADMIN_PASSWORD` | Admin dashboard password |
|
||||
| `R2_ACCESS_KEY_ID` | Cloudflare R2 access key |
|
||||
| `R2_SECRET_ACCESS_KEY` | Cloudflare R2 secret key |
|
||||
| `R2_BUCKET_NAME` | R2 bucket name |
|
||||
| `R2_ENDPOINT` | R2 S3-compatible endpoint URL |
|
||||
| `R2_ADMIN_ACCESS_KEY_ID` | R2 admin access key (optional) |
|
||||
| `R2_ADMIN_SECRET_ACCESS_KEY` | R2 admin secret key (optional) |
|
||||
| `R2_ADMIN_ENDPOINT` | R2 admin endpoint (optional) |
|
||||
| `CLOUDFLARE_API_TOKEN` | Cloudflare API token (optional) |
|
||||
| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID (optional) |
|
||||
| `CI_REGISTRY_USER` | Gitea container registry username |
|
||||
| `CI_REGISTRY_TOKEN` | Gitea container registry token |
|
||||
`DATABASE_URL`, `ADMIN_USERNAME`, `ADMIN_PASSWORD`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET_NAME`, `R2_ENDPOINT`, `R2_ADMIN_ACCESS_KEY_ID` (optional), `R2_ADMIN_SECRET_ACCESS_KEY` (optional), `R2_ADMIN_ENDPOINT` (optional), `CLOUDFLARE_API_TOKEN` (optional), `CLOUDFLARE_ACCOUNT_ID` (optional), `CI_REGISTRY_USER`, `CI_REGISTRY_TOKEN`
|
||||
|
||||
## Pull-and-Run Deployment
|
||||
|
||||
Images are built and pushed automatically by the CI and release workflows (see [CONTRIBUTING.md](../CONTRIBUTING.md#versioning-and-releases) for the release procedure).
|
||||
|
||||
Every push to `main` triggers CI (builds `:main` and `:latest` tags) and CD (deploys `:latest` to test lab) in parallel. Semantic version tags (`*.*.*`, e.g. `1.0.9`) trigger the release workflow which builds `:X.Y.Z`, `:X.Y`, and `:latest` tags and creates a Gitea Release.
|
||||
|
||||
From repository root:
|
||||
Images are built/pushed automatically by CI and release workflows. Every push to `main` triggers CI (`:main`, `:latest`) and CD (deploys `:latest` to test lab) in parallel. Semantic version tags (`*.*.*`, e.g. `1.0.9`) trigger the release workflow (`:X.Y.Z`, `:X.Y`, `:latest` + Gitea Release).
|
||||
|
||||
```bash
|
||||
docker login git.allucanget.biz
|
||||
|
||||
docker compose -f docker-compose.prod.yml pull
|
||||
docker compose -f docker-compose.prod.yml up -d --remove-orphans
|
||||
```
|
||||
|
||||
## First-Time or Recovery Provisioning
|
||||
|
||||
1. Ensure host paths exist:
|
||||
|
||||
```bash
|
||||
mkdir -p data/chat data/playlist data/media data/hls
|
||||
```
|
||||
|
||||
1. Confirm volumes and networks:
|
||||
|
||||
```bash
|
||||
docker volume ls
|
||||
docker network ls
|
||||
```
|
||||
|
||||
1. Start stack:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
## Health Verification
|
||||
|
||||
After startup, check service status:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml ps
|
||||
```
|
||||
|
||||
Endpoint checks:
|
||||
|
||||
- App: <http://{host}:8080/>
|
||||
- Nginx health: <http://{host}:8080/health>
|
||||
- Chat health: <http://{host}:3000/health>
|
||||
- Playout health: <http://{host}:8081/health>
|
||||
- Prometheus: <http://{host}:9090>
|
||||
- Grafana: <http://{host}:3001>
|
||||
- App: `http://{host}:8080/`
|
||||
- Nginx health: `http://{host}:8080/health`
|
||||
- Chat health: `http://{host}:3000/health`
|
||||
- Playout health: `http://{host}:8081/health`
|
||||
- Prometheus: `http://{host}:9090`
|
||||
- Grafana: `http://{host}:3001`
|
||||
|
||||
Domain checks:
|
||||
|
||||
- <https://linearvoid.tv>
|
||||
- <https://linearvoid.tv/admin>
|
||||
- <https://linearvoid.tv/metrics>
|
||||
- `https://linearvoid.tv`
|
||||
- `https://linearvoid.tv/admin`
|
||||
- `https://linearvoid.tv/metrics`
|
||||
|
||||
## Zero/Low-Downtime Update Pattern
|
||||
|
||||
1. Pull newest images.
|
||||
1. Recreate changed services only:
|
||||
|
||||
2. Recreate changed services only:
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml pull
|
||||
|
||||
docker compose -f docker-compose.prod.yml up -d nginx chat playout
|
||||
```
|
||||
|
||||
1. Re-run health checks.
|
||||
3. Re-run health checks.
|
||||
|
||||
## Rollback
|
||||
|
||||
If new images are unhealthy:
|
||||
|
||||
1. Pin previous tags in docker-compose.prod.yml.
|
||||
1. Pin previous tags in `docker-compose.prod.yml`.
|
||||
2. Pull pinned tags.
|
||||
3. Restart impacted services.
|
||||
|
||||
3. Restart impacted services:
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml pull
|
||||
|
||||
docker compose -f docker-compose.prod.yml up -d nginx chat playout
|
||||
```
|
||||
|
||||
## Operational Tasks
|
||||
|
||||
### View logs
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
docker compose -f docker-compose.prod.yml logs -f nginx chat playout
|
||||
```
|
||||
|
||||
### Restart one service
|
||||
|
||||
```bash
|
||||
# Restart one service
|
||||
docker compose -f docker-compose.prod.yml restart chat
|
||||
```
|
||||
|
||||
### Stop stack
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml down
|
||||
```
|
||||
|
||||
### Keep volumes (recommended) vs remove volumes (destructive)
|
||||
|
||||
```bash
|
||||
# keep data
|
||||
# Stop stack (keep data)
|
||||
docker compose -f docker-compose.prod.yml down
|
||||
|
||||
# destructive (remove data)
|
||||
# Stop stack — destructive (remove data)
|
||||
docker compose -f docker-compose.prod.yml down -v
|
||||
```
|
||||
|
||||
## Nginx and Proxy Considerations
|
||||
|
||||
- If an external proxy (for example Nginx Proxy Manager) fronts the stack, align request body limits with internal nginx, see [nginx.conf](../infrastructure/nginx/nginx.conf).
|
||||
- For resumable admin uploads, ensure client_max_body_size supports chunk requests in both proxy layers.
|
||||
- If an external proxy (e.g. Nginx Proxy Manager) fronts the stack, align request body limits with internal nginx (see `infrastructure/nginx/nginx.conf`).
|
||||
- For resumable admin uploads, ensure `client_max_body_size` supports chunk requests in both proxy layers.
|
||||
|
||||
## Security Baseline
|
||||
|
||||
- Use strong ADMIN_PASSWORD and rotate regularly.
|
||||
- Use a strong `ADMIN_PASSWORD` and rotate regularly.
|
||||
- Keep Docker host access restricted.
|
||||
- Prefer HTTPS/WSS through Cloudflare.
|
||||
- Restrict exposed ports if only proxy access is needed.
|
||||
|
||||
## Common Deployment Pitfalls
|
||||
|
||||
- Missing frontend build artifacts in local-only compose workflows.
|
||||
- Wrong registry credentials or missing login.
|
||||
- Misaligned domain env vars (app/ws/hls) causing mixed-origin errors.
|
||||
|
||||
## Troubleshooting
|
||||
| Symptom | Check | Fix |
|
||||
|---|---|---|
|
||||
| Service not starting | `docker logs linearvoid-<svc> --tail 50` | Check `.env` vars, volume mounts, port conflicts |
|
||||
| Playout stall / slate mode | `curl http://<host>:8081/health` — mode=slate | Check playlist file, media paths, disk space |
|
||||
| Chat not connecting | `curl http://<host>:3000/health` | Check Redis: `docker exec linearvoid-redis redis-cli ping` |
|
||||
| Admin 401 loop | Cookie not sent | Verify `X-Forwarded-Proto: https`, nginx auth_request headers |
|
||||
| Upload fails (413) | File too large | Check nginx `client_max_body_size` (should be 64m+) |
|
||||
| HLS not playing | `curl http://<host>/hls/playlist.m3u8` | Check playout health, HLS dir permissions |
|
||||
| Prometheus no targets | `curl http://<host>:9090/targets` | Verify container network, scrape config |
|
||||
| Grafana no data | Check datasource | Verify Prometheus URL `http://prometheus:9090` provisioned |
|
||||
|
||||
| Symptom | Check | Fix |
|
||||
| -------------------------- | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| Service not starting | `docker logs linearvoid-<svc> --tail 50` | Check `.env` vars, volume mounts, port conflicts |
|
||||
| Playout stall / slate mode | `curl http://<host>:8081/health` — mode=slate | Check playlist file, media paths, disk space |
|
||||
| Chat not connecting | `curl http://<host>:3000/health` | Check Redis: `docker exec linearvoid-redis redis-cli ping` |
|
||||
| Admin 401 loop | Cookie not sent | Verify `X-Forwarded-Proto: https`, nginx auth_request headers |
|
||||
| Upload fails (413) | File too large | Check nginx `client_max_body_size` (should be 64m+) |
|
||||
| HLS not playing | `curl http://<host>/hls/playlist.m3u8` | Check playout health, HLS dir permissions |
|
||||
| Prometheus no targets | `curl http://<host>:9090/targets` | Verify container network, scrape config |
|
||||
| Grafana no data | Check datasource | Verify Prometheus URL `http://prometheus:9090` provisioned |
|
||||
|
||||
- Proxy-layer body size limits returning 413 on upload.
|
||||
- Health checks failing before service warmup is complete.
|
||||
Additional notes: proxy-layer body size limits can return 413 on upload; health checks may fail before service warmup completes.
|
||||
Reference in new issue
Block a user