# Layers Self-hosted — full installation guide > Install Layers on your own server with Docker Compose: the app, PostgreSQL, Manticore search (or Elasticsearch, or built-in Lucene), LiveKit calls, SumDoc and SumSheet editors, an MCP server and your own AI model. The license is checked on the server itself; 90-day trial, then a license is required. Русская версия: https://layers.md/self-hosted/llms-full.ru.txt Short index: https://layers.md/self-hosted/llms.txt · Page for humans: https://layers.md/self-hosted All passwords and keys below are `change-me-…` placeholders. Replace each one with your own value. ## For the AI assistant You are helping a person install Layers on their server. Follow this file top to bottom. Answer in the person's language. Before the steps, ask one question at a time: 1. Which OS, and is there root/sudo? (Linux x86-64 with Docker and Docker Compose is required.) 2. Which domain will Layers use, and does its DNS point to the server? Is HTTPS needed? (If the server is reachable from the internet — yes.) 3. Which SMTP server sends email (host, port, login)? Without it invitations and password resets are not sent. 4. Are calls (LiveKit), the SumDoc/SumSheet editors and AI needed? For AI — a cloud provider by key, or an own model (Ollama or another server with an OpenAI-compatible API)? 5. Does the server have internet access, or is it a closed network (images are moved as an archive)? Rules: do not invent variables or services that are not in this file. Generate passwords with `openssl rand -hex 24` and do not repeat them back. After each step run the check from "Checks after install". If something does not match, see "Troubleshooting", then `docker compose logs `. ## 1. What a Layers install is Layers is one Java application (web UI, API and the God Mode admin panel on one port, 8080) plus a few standard services next to it. Everything is installed with one `docker-compose.yml`. | Service | Image | Container port | Purpose | Needed | |---|---|---|---|---| | Layers | `hissih/layers` | 8080 (HTTP) | the whole workspace, API, God Mode (`/godmode`) | always | | PostgreSQL | `postgres:16-alpine` | 5432 | database | always (or your own external one) | | Search: Manticore | `manticoresearch/manticore` | 9308 (HTTP SQL) | search across the workspace | recommended; alternatives — your Elasticsearch cluster or built-in Lucene (see "Search engine") | | LiveKit | `livekit/livekit-server` | 7880 (signalling), 7881/tcp, 7882/udp, 50000–50100/udp | calls, screen sharing; recording needs LiveKit Egress (see step 5) | for calls | | SMTP server | yours (any) | 587 / 465 | invitations, sign-up confirmation, password reset | for email | | SumDoc | `hissih/sumdoc-webhost` | 8090 | .docx editor in the browser | for documents | | SumSheet | `hissih/sumsheet-webhost` | 8092 | .xlsx/.xlsm editor in the browser | for spreadsheets | | MCP server | `sinups/layers-mcp-server` | 8091 (`/mcp`) | the assistant and external agents (Claude, Cursor) work with tasks and pages | optional | | LLM | your provider | — | AI: assistant, summaries, answers | optional | What happens without an optional service: - **no LiveKit** — no calls; chats work; - **no SumDoc / SumSheet** — .docx and .xlsx files on Drive open in OnlyOffice if it is installed from the module catalog, otherwise as a file card with a download button; - **no SMTP** — no invitations, sign-up confirmations or password resets; - **no LLM** — no AI features; everything else works; - **no MCP server** — the assistant cannot take actions in the workspace, external agents cannot connect; - **no Manticore** — use your Elasticsearch cluster or built-in Lucene (see "Search engine"); if Manticore is temporarily unavailable, Layers falls back to Lucene by itself. ### Requirements - Linux **x86-64** (the Layers and editor images are built for `linux/amd64`; there are no arm64 images), Docker and Docker Compose. - PostgreSQL (`postgres:16-alpine` in the compose below; an external database works too). - SumDoc / SumSheet editors: each open document is its own process; the browser connects to it over WebSocket. `--max-cabins` limits how many documents are open at once; if the node lacks memory for them, the editor's `/health` reports it in `warnings`. - Files up to 900 MB by default (changed with a variable, see below). Disk space depends on your files: they live in the `layers_data` volume (or in S3). ## 2. Install ### Step 1. Prepare the server ```bash docker --version docker compose version mkdir -p /opt/layers && cd /opt/layers # one password per change-me placeholder openssl rand -hex 24 ``` You need a domain pointing to the server (e.g. `example.com`). Without a domain you can work over `http://IP:8080` inside a network — then use that address instead of `https://example.com` in every variable below. ### Step 2. Create `docker-compose.yml` Save as `/opt/layers/docker-compose.yml`. Replace `example.com` with your domain and every `change-me-…` with your own value. Optional services (calls, editors, MCP) can be removed together with their variables on `layers`. ```yaml name: layers services: db: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: layers POSTGRES_USER: layers POSTGRES_PASSWORD: change-me-db volumes: - pg_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U layers -d layers"] interval: 5s retries: 20 manticore: image: manticoresearch/manticore:latest restart: unless-stopped volumes: - manticore_data:/var/lib/manticore layers: image: hissih/layers:latest platform: linux/amd64 restart: unless-stopped depends_on: db: { condition: service_healthy } manticore: { condition: service_started } ports: - "127.0.0.1:8080:8080" volumes: - layers_data:/root/layers_data environment: SPRING_PROFILES_ACTIVE: server # Database DB_SERVER_URL: jdbc:postgresql://db:5432/layers DB_SERVER_USERNAME: layers DB_SERVER_PASSWORD: change-me-db # Admin panel /godmode ADMIN_USERNAME: admin ADMIN_PASSWORD: change-me-admin # Domain LAYERS_DOMAIN: example.com LAYERS_APP_DOMAIN: example.com APP_REDIRECT_URL: https://example.com APP_CORS_ALLOWED_ORIGINS: https://example.com # Search SEARCH_PROVIDER: manticore SEARCH_MANTICORE_ENABLED: "true" SEARCH_MANTICORE_URL: http://manticore:9308 SEARCH_ELASTICSEARCH_ENABLED: "false" # Calls LIVEKIT_ENABLED: "true" LIVEKIT_URL: ws://livekit:7880 LIVEKIT_PUBLIC_URL: wss://example.com/livekit/ LIVEKIT_API_KEY: change-me-livekit-key LIVEKIT_API_SECRET: change-me-livekit-secret-min-32-characters # Email (outgoing) MAIL_PROVIDER: smtp SPRING_MAIL_HOST: smtp.example.com SPRING_MAIL_PORT: "587" SPRING_MAIL_USERNAME: noreply@example.com SPRING_MAIL_PASSWORD: change-me-smtp SPRING_MAIL_SMTP_AUTH: "true" SPRING_MAIL_SMTP_STARTTLS_ENABLE: "true" # SumDoc and SumSheet editors (addresses the browser sees) LAYERS_FASTDOC_URL: https://example.com/sumdoc LAYERS_FASTSHEET_URL: https://example.com/sumsheet # AI: the model is connected in God Mode → AI AI_ENABLED: "true" ASSISTANT_MCP_URL: http://mcp:8091/mcp AI_USER_MODEL_CHOICE: "true" livekit: image: livekit/livekit-server:latest restart: unless-stopped environment: LIVEKIT_CONFIG: | port: 7880 bind_addresses: [""] rtc: tcp_port: 7881 port_range_start: 50000 port_range_end: 50100 use_external_ip: true keys: change-me-livekit-key: change-me-livekit-secret-min-32-characters webhook: api_key: change-me-livekit-key urls: - http://layers:8080/v1/livekit/webhook ports: - "7880:7880" - "7881:7881" - "7882:7882/udp" - "50000-50100:50000-50100/udp" sumdoc: image: hissih/sumdoc-webhost:latest platform: linux/amd64 restart: unless-stopped command: ["--base-path", "/sumdoc", "--max-cabins", "12", "--idle-min", "20", "--auth-url", "http://layers:8080/v1/me", "--attach-url", "http://layers:8080/v1/attachments/{id}/content"] ports: - "127.0.0.1:8090:8090" volumes: - sumdoc_cabins:/data/cabins sumsheet: image: hissih/sumsheet-webhost:latest platform: linux/amd64 restart: unless-stopped command: ["--base-path", "/sumsheet", "--max-cabins", "12", "--idle-min", "20", "--auth-url", "http://layers:8080/v1/me", "--attach-url", "http://layers:8080/v1/attachments/{id}/content"] ports: - "127.0.0.1:8092:8092" volumes: - sumsheet_cabins:/data/cabins mcp: image: sinups/layers-mcp-server:latest # the same image the God Mode module catalog installs restart: unless-stopped depends_on: [layers] environment: SPRING_PROFILES_ACTIVE: server SERVER_PORT: "8091" LAYERS_BACKEND_BASEURL: http://layers:8080 LAYERS_BACKEND_FORWARDINCOMINGAUTH: "true" LAYERS_BACKEND_REQUIREINCOMINGAUTH: "true" ports: - "127.0.0.1:8091:8091" volumes: pg_data: manticore_data: layers_data: sumdoc_cabins: sumsheet_cabins: ``` Important: - The Layers volume must be mounted at exactly `/root/layers_data`. It holds files, God Mode settings (`application-external.properties`), JWT secrets and email templates. Any other path and settings are lost on update. - `POSTGRES_PASSWORD` and `DB_SERVER_PASSWORD` must match. PostgreSQL takes the password only when the volume is created. - The LiveKit key and secret must match in `LIVEKIT_CONFIG` and on `layers`. The secret must be at least 32 characters. - Port 8080 is bound to `127.0.0.1` only: from outside, Layers is reached through nginx (step 4). ### Search engine: Manticore, Elasticsearch or Lucene Layers ships three search engines; the choice is the `SEARCH_PROVIDER` variable (`search.provider`): `auto`, `manticore`, `elasticsearch` or `lucene`. - **Manticore** — recommended: a separate lightweight container, already in the compose above. - **Elasticsearch** — if your company already runs a cluster. Remove the `manticore` service and its `depends_on`, and set on `layers`: ```yaml SEARCH_PROVIDER: elasticsearch SEARCH_ELASTICSEARCH_ENABLED: "true" SEARCH_MANTICORE_ENABLED: "false" ES_HOST: elasticsearch.example.com ES_PORT: "9200" ES_PROTOCOL: https # default: http ES_USERNAME: layers ES_PASSWORD: change-me-es ``` - **Lucene** — built in, no separate service. The index lives in the Layers file directory (`/lucene_index_v5`, i.e. inside the `layers_data` volume). Fits a small install: ```yaml SEARCH_PROVIDER: lucene SEARCH_MANTICORE_ENABLED: "false" SEARCH_ELASTICSEARCH_ENABLED: "false" ``` After switching engines, run a full reindex: God Mode → Platform → Search. ### Step 3. Start ```bash cd /opt/layers docker compose up -d docker compose logs -f layers # wait for "Started LayersApplication" (15–60 s) curl -s http://127.0.0.1:8080/health # OK ``` Open `https://example.com` (or `http://IP:8080`) and create the first account. Admin panel: `https://example.com/godmode/login`, user `admin` and your `ADMIN_PASSWORD`. ### Step 4. HTTPS with nginx Layers serves everything itself on port 8080 — a separate web server is needed only to terminate TLS. Inside a local network or behind a corporate proxy nginx is optional: point the proxy at port 8080. ```bash sudo apt install nginx certbot python3-certbot-nginx sudo certbot certonly --nginx -d example.com ``` `/etc/nginx/sites-available/layers.conf`: ```nginx server { listen 80; server_name example.com; location /.well-known/acme-challenge/ { root /var/www/certbot; } location / { return 301 https://example.com$request_uri; } } server { listen 443 ssl http2; server_name example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; client_max_body_size 1000m; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; # Layers: web, API, God Mode 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-Proto https; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 300; } # LiveKit: WebSocket signalling (the trailing slash in proxy_pass matters) location /livekit/ { proxy_pass http://127.0.0.1:7880/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 86400s; } # LiveKit REST: the server SDK calls /twirp/ on the root location /twirp/ { proxy_pass http://127.0.0.1:7880/twirp/; proxy_set_header Host $host; proxy_read_timeout 86400s; } # Editors: same domain, so the editor receives the Layers sign-in cookie location /sumdoc { proxy_pass http://127.0.0.1:8090; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; } location /sumsheet { proxy_pass http://127.0.0.1:8092; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; } # MCP for external agents (optional) location /mcp { proxy_pass http://127.0.0.1:8091/mcp; proxy_http_version 1.1; proxy_set_header Host $host; proxy_buffering off; proxy_read_timeout 3600s; } } ``` ```bash sudo ln -s /etc/nginx/sites-available/layers.conf /etc/nginx/sites-enabled/ sudo rm -f /etc/nginx/sites-enabled/default sudo nginx -t && sudo systemctl reload nginx sudo certbot renew --dry-run ``` Do not add `limit_req` (the UI loads dozens of files per page; brute-force protection is built in) and do not serve static files from nginx — Layers sets cache headers itself. ### Step 5. Calls (LiveKit) Open the media ports on the server firewall: ```bash sudo ufw allow 7881/tcp sudo ufw allow 7882/udp sudo ufw allow 50000:50100/udp ``` In God Mode → **Apps → Integrations → LiveKit**: WebSocket URL `wss://example.com/livekit/` (trailing slash required), key and secret as in compose. Save → Test connection → Enable. If the LiveKit log says `could not validate external IP` (the host blocks outbound UDP to STUN), replace `use_external_ip: true` with `node_ip: ` in `LIVEKIT_CONFIG`. In a closed network, `node_ip` is the server's address in that network. Call recording on your own server is not limited by plan, but it needs a separate LiveKit Egress service (with Redis, per the LiveKit documentation); it is not in this compose. By default only audio is recorded (`LIVEKIT_RECORDING_AUDIO_ONLY`, default `true`). A group moderator turns recording on. The UDP range 50000–50100 is enough for normal load; for many simultaneous calls widen it in `LIVEKIT_CONFIG` and in `ports` at the same time. ### Step 6. Email Two separate things: 1. **Emails from Layers** (invitations, confirmations, password resets) — the SMTP settings from step 2 (`MAIL_PROVIDER: smtp`). Port 587 uses STARTTLS; for 465 set `SPRING_MAIL_PROPERTIES_MAIL_SMTP_SSL_ENABLE: "true"` instead of STARTTLS. The same settings are in God Mode → **Platform → Email**, with a test email and email templates. Other providers: `MAIL_PROVIDER: sendgrid` (`SENDGRID_API_KEY`, `SENDGRID_FROM`) or `mailchimp` (`MAILCHIMP_API_KEY`). 2. **The Mail module** — an email client inside Layers. People connect their existing mailboxes over IMAP/SMTP. Layers does not run a mail server of its own. ### Step 7. SumDoc and SumSheet editors They are already in compose. The editor stores no users and no files: it asks Layers who the person is (`/v1/me`, with the user's cookie) and fetches the file by id (`/v1/attachments/{id}/content`). That is why the editors are served on the same domain as Layers (`/sumdoc`, `/sumsheet` in nginx). Check: ```bash curl -s http://127.0.0.1:8090/sumdoc/health # "status": "ok", …, "version": {…, "skewMin": 0} curl -s http://127.0.0.1:8092/sumsheet/health # "status": "ok", …, "version": {…, "skewMin": 0} curl -s https://example.com/v1/capabilities # {"fastdoc":{"enabled":true,"url":"https://example.com/sumdoc"},"fastsheet":{"enabled":true,"url":"https://example.com/sumsheet"},…} ``` `skewMin: 0` (in the `version` block) means editor and engine come from the same release. `--max-cabins` sets how many documents can be open at once. Without `LAYERS_FASTDOC_URL` / `LAYERS_FASTSHEET_URL`, Drive opens such files in OnlyOffice if it is installed from the module catalog, otherwise as a file card with a download button. ### Step 8. AI: your own model or a provider The model is connected in God Mode → **AI → Providers → Add provider**. Types: OpenAI, OpenRouter, Gonka and **Own connection** — any server with an OpenAI-compatible API, such as Ollama. A key is optional for an own connection. Enter the address and press Test — Layers lists the models the server offers; pick the ones you need and enable the provider. Provider capabilities: chat, tools, transcription, embeddings, reasoning. A local model with Ollama in the same compose: ```yaml ollama: image: ollama/ollama:latest restart: unless-stopped volumes: - ollama_data:/root/.ollama # and under volumes: # ollama_data: ``` ```bash docker compose up -d ollama docker compose exec ollama ollama pull gpt-oss:20b ``` Address for the own connection: `http://ollama:11434/v1`. If Ollama or another server with an OpenAI-compatible API runs on another machine, use its address ending in `/v1`, for example `http://:11434/v1` for Ollama. Choose the model to fit the memory of the machine that runs it. AI variables on `layers`: | Variable | What it does | |---|---| | `AI_ENABLED` | turns AI features on | | `ASSISTANT_MCP_URL` | MCP server address the assistant uses to take actions in the workspace (`http://mcp:8091/mcp`) | | `AI_USER_MODEL_CHOICE` | people can choose the model in the assistant | External agents (Claude, Cursor and other MCP clients) connect to `https://example.com/mcp` with a personal token: in Layers → **Settings → Access Tokens → Add token** (read-only or read and write, with an expiry). ### Step 9. First sign-in to God Mode `https://example.com/godmode/login` → `admin` / your `ADMIN_PASSWORD`. **Getting started** is the server setup checklist: domains and addresses, email, sign-in, modules, branding. Each item is marked done from the server's actual state. Panel sections: System (domains and addresses, database, SSL, file storage, restart, God Mode access, license), Platform (email, search, error monitoring, analytics, infrastructure services), Product (modules, branding, legal documents, authentication), AI, Apps (app store, integrations), People (users, audit), Operations (system health, cluster, email queue, feature flags, quotas). The module catalog (God Mode → Apps → App Store) installs modules by pulling their images from the Docker registry. It only works when the Layers container has Docker access (`LAYERS_APPSTORE_DOCKER_ENABLED: "true"` and the Docker socket, see deploy/DOCKER.md); without access to the registry, installing modules from the catalog is unavailable. Sign-in methods (God Mode → Product → Authentication, or variables): login and password, Google (`GOOGLE_AUTH_ENABLED`, `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REDIRECT_URI`), Yandex (`YANDEX_AUTH_ENABLED`, …), VK (`VK_AUTH_ENABLED`, …), Gosuslugi/ESIA (`ESIA_AUTH_ENABLED`, …). SAML, LDAP and SCIM are not supported. ### Step 10. License The trial period is **90 days**; after that a license is required. The count starts from the earliest workspace on the server. The license is checked on the server itself. Without a key after the trial, the server does not block sign-in or data (`regime` → `quiet`); machine jobs run slower. The key is one line. God Mode → **System → License → Paste the key → Apply key**. ```bash curl -s https://example.com/layers/license/status # {"mode":"MISSING",…,"node":{"regime":"evaluation","day":2,"evaluationDaysLeft":89,…}} ``` With a key, `regime` becomes `full`. Key: contacts@layers.md. ### Step 11. Updates ```bash cd /opt/layers docker compose pull docker compose up -d ``` Data, files and God Mode settings stay in the `pg_data` and `layers_data` volumes. If a new version adds a new service, `pull` will not add it — add the service block to compose and run `docker compose up -d` again. For predictable updates pin the image tag (e.g. `hissih/layers:2.1.184-unified-20261001-v13`) instead of `latest`. The running tag is shown in God Mode → Operations → System health. ### Step 12. Backups There is no built-in database backup in the panel — use PostgreSQL tools and a copy of the files volume. ```bash cd /opt/layers # database docker compose exec -T db sh -c 'pg_dump -U $POSTGRES_USER $POSTGRES_DB' | gzip > layers-db-$(date +%F).sql.gz # files, God Mode settings, JWT secrets docker run --rm -v layers_layers_data:/data -v "$PWD":/backup alpine \ tar czf /backup/layers-data-$(date +%F).tar.gz -C /data . ``` Restore the database into an empty volume: ```bash gunzip -c layers-db-2026-10-02.sql.gz | docker compose exec -T db sh -c 'psql -U $POSTGRES_USER $POSTGRES_DB' ``` The volume name is `_layers_data`; with `name: layers` in compose it is `layers_layers_data` (check: `docker volume ls`). ### Closed network Pull the images on a machine with internet access and move them as an archive: ```bash IMAGES="hissih/layers:latest postgres:16-alpine manticoresearch/manticore:latest livekit/livekit-server:latest \ hissih/sumdoc-webhost:latest hissih/sumsheet-webhost:latest sinups/layers-mcp-server:latest" for i in $IMAGES; do docker pull "$i"; done docker save $IMAGES | gzip > layers-images.tar.gz # on the server inside the closed network gunzip -c layers-images.tar.gz | docker load docker compose up -d ``` What works in a closed network: - the license is checked on the server itself; - LiveKit, with `node_ip` = the server address in that network (step 5); - AI only with your own model inside that network: Ollama or another server with an OpenAI-compatible API (step 8). The module catalog is unavailable without access to the Docker registry: modules from it will not install. Sign-in with Google, Yandex, VK and Gosuslugi, as well as Unsplash and GIPHY, need access to those services. ## 3. Useful variables | Variable | Default | What it does | |---|---|---| | `APP_JWT_SECRET` | generated automatically and stored in `layers_data` | session secret; set explicitly only if needed (`openssl rand -hex 64`) | | `FILE_STORAGE_TYPE` | `LOCAL` | `S3` — files in S3-compatible storage (`USE_AWS`, `S3_BUCKET_NAME`, `S3_REGION`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`); also God Mode → System → File storage | | `SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE`, `SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE` | 900MB | file size limit; nginx `client_max_body_size` must be at least as large | | `FRONTEND_APP_NAME`, `FRONTEND_LOGO_FULL_URL`, `FRONTEND_LOGO_ICON_URL` | — | name and logo (also God Mode → Product → Branding) | | `USE_SENTRY`, `SENTRY_DSN` | off | send server errors to your Sentry | | `UNSPLASH_ENABLED` / `GIPHY_ENABLED` | off | image and GIF search (needs keys and internet) | | `REINDEX_ON_STARTUP` | `false` | full search reindex on start | Everything set by variables can also be changed in God Mode. Variables apply at start and override values from the panel. ## 4. Checks after install ```bash docker compose ps # all services Up, db healthy curl -s https://example.com/health # OK curl -s https://example.com/layers/license/status # "regime":"evaluation" or "full" curl -s https://example.com/v1/capabilities # fastdoc / fastsheet: "enabled": true curl -s http://127.0.0.1:8090/sumdoc/health # "status": "ok" curl -s http://127.0.0.1:8092/sumsheet/health # "status": "ok" curl -s http://127.0.0.1:8091/actuator/health # MCP: "status":"UP" ``` In the UI: 1. God Mode → Getting started: every item done. 2. Platform → Email: the test email arrives. 3. Integrations → LiveKit: Test connection → green notice; a call between two browsers works. 4. On Drive, .docx opens in SumDoc and .xlsx in SumSheet. 5. AI → the provider is Verified, models selected; the assistant answers. ## 5. Troubleshooting | Symptom | Cause and fix | |---|---| | `password authentication failed` | the PostgreSQL password was changed after the volume was created. Keep data: `docker compose exec db psql -U layers -d layers -c "ALTER USER layers PASSWORD 'new'"`; or recreate: `docker compose down -v` (deletes data) | | Server does not start: `workspaces_plan_check` | `docker compose exec db psql -U layers -d layers -c "ALTER TABLE workspaces DROP CONSTRAINT IF EXISTS workspaces_plan_check;"` then `docker compose restart layers` | | 502/503 after an update | the app needs 15–60 s to start; `docker compose logs layers --tail=20`, wait for "Started LayersApplication" | | CORS errors in the browser | `APP_CORS_ALLOWED_ORIGINS` must exactly match the address in the browser (scheme and port) | | Upload fails: `ERR_NOT_MULTIPART` or 413 | raise `client_max_body_size` in nginx and, if needed, `SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE` | | Everyone is signed out after a restart | the `layers_data` volume (holds the JWT secret) was lost or is not mounted at `/root/layers_data` | | A call connects but there is no audio or video | UDP 50000–50100 or 7882 is closed; `could not validate external IP` — set `node_ip` | | God Mode: LiveKit "unreachable", HTTP 401 with correct keys | `location /twirp/` is missing in nginx; check `GET /v1/livekit/status` | | `baseUrl must end in /` | the LiveKit address in God Mode lacks the trailing slash: use `wss://example.com/livekit/` | | A document opens as a card with "Download" or in OnlyOffice | `LAYERS_FASTDOC_URL` / `LAYERS_FASTSHEET_URL` not set or the editor is not running — check `/v1/capabilities` | | The editor shows a sign-in error | the editor is not served on the Layers domain (no cookie) or `--auth-url` cannot reach `layers:8080` | | Search finds nothing after switching engines | reindex in God Mode → Platform → Search | | The model is not listed in the custom connection | the address must end with `/v1` and be reachable from the `layers` container (a service name or IP, not `localhost`) | ## 6. Where data lives | Data | Where | |---|---| | database | `pg_data` volume | | files, God Mode settings, secrets, email templates | `layers_data` volume → `/root/layers_data` | | search index | `manticore_data` volume; with Lucene — inside `layers_data`; with Elasticsearch — in your cluster (all can be rebuilt by reindexing) | | temporary copies of documents open in the editors | `sumdoc_cabins`, `sumsheet_cabins` volumes | Questions and license keys: contacts@layers.md