This repository now supports a full Docker Compose deployment:
postgresredislivekitserver(Rust backend)web(React static build served by Nginx)
- Docker Engine + Docker Compose v2
- A copied
.envfile from.env.example
git clone https://github.com/emircanagac/voxpery.git
cd voxpery
cp .env.example .envEdit .env and set strong production values at minimum:
POSTGRES_PASSWORDJWT_SECRETLIVEKIT_API_SECRETLIVEKIT_NODE_IP(server public IPv4)ADMIN_PASSWORDCOOKIE_SECURE=1(when using HTTPS)CORS_ORIGINSwith your production origins onlyVITE_API_URL(public backend URL used by frontend build)ATTACHMENTS_PUBLIC_BASE_URL(for uploaded file URLs; usually your API domain)
LiveKit note:
- Compose uses
use_external_ip: false(deterministic mode). - Set
LIVEKIT_NODE_IPin production to avoid external IP discovery failures in containerized deployments.
Attachments note:
- Uploads are local-only and served via signed URLs under
/api/attachments/content/*. - Configure with:
ATTACHMENTS_LOCAL_DIRATTACHMENTS_KEY_PREFIXATTACHMENTS_PUBLIC_BASE_URLATTACHMENTS_URL_TTL_SECS
Optional integrations note:
- Google OAuth is disabled unless
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETare both set. - Email delivery is disabled unless
SMTP_HOST,SMTP_USER, andSMTP_PASSWORDare all set. - Password reset and email verification depend on email delivery.
- The backend publishes integration availability at
/api/system/features; web and desktop clients hide unavailable flows. - Direct calls to disabled integration endpoints return
FEATURE_DISABLED. EMAIL_VERIFICATION_REQUIRED=truerequires SMTP email delivery and fails startup if email delivery is not configured.- Keep disabled optional integrations commented out in
.env; do not set optional variables to empty values.
Before production changes, validate the default self-host flow from a clean environment file:
cp .env.example .env
# Edit `.env` and replace every CHANGE_ME value.
docker compose config >/dev/null
docker compose up -d --build
docker compose psBasic service checks:
curl -f http://localhost:3001/health
curl -I http://localhost:${WEB_PORT:-5173}
curl -s http://localhost:3001/api/system/featuresExpected default integration state:
google_oauth_enabledisfalse.email_delivery_enabledisfalse.email_verification_enabledisfalse.password_reset_enabledisfalse.- The web and desktop UI should hide Google sign-in, password reset, and email verification prompts until those integrations are configured.
Manual product checks:
- Register/login works with the seeded admin account or a new local account.
- Server, channel, and category navigation work.
- Voice join reaches LiveKit.
- Attachment upload and signed attachment viewing work.
docker compose up -d --build
docker compose psClamAV is disabled by default in compose. To use malware scanning in production:
ATTACHMENTS_CLAMAV_ENABLED=1 docker compose --profile security up -dOr set ATTACHMENTS_CLAMAV_ENABLED=1 in .env and always deploy with --profile security.
The bundled GitHub Actions production deploy workflow expects your production .env to already contain the correct attachment scanning settings and deploys with docker compose --profile security ....
Default ports:
- Web:
http://localhost:${WEB_PORT:-5173} - API:
http://localhost:3001 - Postgres:
localhost:5432 - Redis:
localhost:6379 - LiveKit:
localhost:7880
Security defaults in compose:
web,server,postgres,redis,livekit:7880bind to127.0.0.1only- Public media ports stay open for LiveKit:
7881/tcp(fallback)7882/udp50000-50200/udp
- Container logs use rotation (
max-size=10m,max-file=5) to avoid disk growth
Important:
- The default
docker-compose.ymlapplies conservative LiveKit limits even if you do not customize.env. - Default values are:
LIVEKIT_CPUS_LIMIT=2.0LIVEKIT_MEM_LIMIT=1500mLIVEKIT_PIDS_LIMIT=512
- You can override these values from
.envon smaller or larger hosts. - These limits reduce single-host blast radius (LiveKit cannot consume all CPU/RAM/PIDs).
- They do not protect against upstream bandwidth saturation from large volumetric UDP floods.
curl -f http://localhost:3001/health
curl -I http://localhost:${WEB_PORT:-5173}The public API health response intentionally stays minimal and should only report status=ok. Use scripts/ops/stack_healthcheck.sh for server-side dependency checks.
Manual checks:
- Register/login works
- Server/channel/category permissions work
- Voice join works
- Moderation actions (kick/ban) work
git pull
docker compose up -d --buildYou can prebuild and push images, then let Compose pull them during deploy instead of rebuilding on the server.
Recommended tagging strategy:
latestfor rolling production deployssha-<commit>when you want to pin an exact build
Example:
docker build -t <dockerhub-user>/voxpery-server:<tag> ./apps/server
docker build -t <dockerhub-user>/voxpery-web:<tag> ./apps/web
docker push <dockerhub-user>/voxpery-server:<tag>
docker push <dockerhub-user>/voxpery-web:<tag>Then set these optional variables in .env (or directly on the server):
VOXPERY_SERVER_IMAGE=<dockerhub-user>/voxpery-server:<tag>
VOXPERY_WEB_IMAGE=<dockerhub-user>/voxpery-web:<tag>Current production deploy workflow can then use:
docker compose pull server web
docker compose up -d --no-build --remove-orphans./scripts/ops/db_backup.shRestore (explicit confirmation required):
RESTORE_CONFIRM=YES ./scripts/ops/db_restore.sh backups/postgres/<backup-file>.sql.gzHealthcheck:
./scripts/ops/stack_healthcheck.sh
./scripts/ops/critical_log_scan.shFor production operations, alerting and restore drill checklist, see:
docs/OPERATIONS_RUNBOOK.md
- LiveKit runs on bridge networking with explicit port mappings for cross-platform compatibility.
- Backend migrations run automatically on startup.
- Frontend is built at image build time, so changing
VITE_API_URLrequires rebuilding thewebimage.