Docker Installation Guide

Deploy ManageLM on your own infrastructure with Docker.

Image variant:

Overview

ManageLM is published as managelm/portal on Docker Hub with two variants:

ImageIncludesBest for
managelm/portal:latestPortal onlyProduction — bring your own PostgreSQL & Redis
managelm/portal:allinonePortal + PostgreSQL + RedisQuick evaluation, testing

ManageLM Only (Recommended)

The ManageLM Only image (managelm/portal:latest) contains only the ManageLM application. You provide your own PostgreSQL and Redis instances — either as separate Docker containers (included in the default compose file) or as external/managed services. This gives you full control over database configuration, backups, scaling, and upgrades independently of the portal.

All-in-One (with DB)

The All-in-One (with DB) image (managelm/portal:allinone) bundles the portal, PostgreSQL, and Redis into a single container. It is the fastest way to try ManageLM — one container, no external dependencies — but it is not recommended for production because database and cache lifecycle are tied to the container, making independent scaling, backups, and upgrades harder.

Prefer not to self-host? ManageLM is available as a managed SaaS at app.managelm.com.

Requirements

Quick Start

Single container — no external dependencies.

  1. Download the compose file
    mkdir managelm && cd managelm
    curl -O https://app.managelm.com/doc/docker-compose.allinone.yml
    mv docker-compose.allinone.yml docker-compose.yml
  2. Edit docker-compose.yml — update these values:
    • SERVER_URL — how you reach the portal (e.g. http://192.168.1.10:3000)
    • JWT_SECRET — run openssl rand -hex 32
    • SMTP_FROM — sender email address
    • POSTGRES_PASSWORD — choose a strong password
  3. Start
    docker compose up -d
  4. Open the portal — Navigate to your SERVER_URL and register. The first user becomes the account owner.

Portal + PostgreSQL + Redis as separate containers.

  1. Download the compose file
    mkdir managelm && cd managelm
    curl -O https://app.managelm.com/doc/docker-compose.yml
  2. Edit docker-compose.yml — update these values:
    • SERVER_URL — how you reach the portal (e.g. http://192.168.1.10:3000)
    • JWT_SECRET — run openssl rand -hex 32
    • SMTP_FROM — sender email address
    • POSTGRES_PASSWORD — set the same password in DATABASE_URL and in the postgres service
  3. Start
    docker compose up -d
  4. Open the portal — Navigate to your SERVER_URL and register. The first user becomes the account owner.
Using your own PostgreSQL / Redis? Remove the postgres and redis services from the compose file and set DATABASE_URL and REDIS_URL directly in the portal's environment section.

External Database Setup

When using your own PostgreSQL or Redis instead of the containers from the compose file, follow these steps to prepare them before starting the portal.

PostgreSQL

ManageLM requires PostgreSQL 15+ (16 recommended). The portal runs migrations automatically on startup and needs full control over its schema.

  1. Create the database and user
    # Connect as a PostgreSQL superuser (e.g. postgres)
    sudo -u postgres psql
    
    -- Create the ManageLM user
    CREATE USER managelm WITH PASSWORD 'your-strong-password';
    
    -- Create the database owned by that user
    CREATE DATABASE managelm OWNER managelm;
    
    -- Connect to the new database
    \c managelm
    
    -- Grant full schema permissions (required for migrations)
    GRANT ALL ON SCHEMA public TO managelm;
    
    -- If on PostgreSQL 15+, also grant CREATE (changed default in PG15)
    ALTER DEFAULT PRIVILEGES IN SCHEMA public
      GRANT ALL ON TABLES TO managelm;
    ALTER DEFAULT PRIVILEGES IN SCHEMA public
      GRANT ALL ON SEQUENCES TO managelm;
    
    \q
  2. Allow remote connections (if PostgreSQL is on a different host)
    # In postgresql.conf — listen on all interfaces
    listen_addresses = '*'
    
    # In pg_hba.conf — allow the portal host (replace with your subnet)
    host  managelm  managelm  10.0.0.0/8  scram-sha-256

    Restart PostgreSQL after editing: systemctl restart postgresql

  3. Set DATABASE_URL in your docker-compose.yml
    DATABASE_URL=postgresql://managelm:your-strong-password@db-host:5432/managelm
  4. Test the connection (from the portal host)
    psql "postgresql://managelm:your-strong-password@db-host:5432/managelm" -c "SELECT 1;"
SSL/TLS: For encrypted connections, set DB_SSL=require in your docker-compose.yml. To verify the server certificate, use DB_SSL=verify-ca with DB_SSL_CA=/path/to/ca.pem (mount the CA file into the container).
PostgreSQL 15+ changed defaults. In PG 15 the CREATE privilege on the public schema was revoked for non-owners. If the portal fails to start with "permission denied for schema public", run: GRANT ALL ON SCHEMA public TO managelm;

Redis

ManageLM requires Redis 7+ (or Valkey). It is used for real-time agent communication, session state, and pub/sub — it must be available at all times.

  1. Enable persistence (recommended)
    # In redis.conf
    appendonly yes
    appendfsync everysec
  2. Set REDIS_URL in your docker-compose.yml
    # Without authentication
    REDIS_URL=redis://redis-host:6379
    
    # With authentication (Redis 6+ ACL)
    REDIS_URL=redis://username:password@redis-host:6379
  3. Test the connection
    redis-cli -h redis-host ping
    # Expected: PONG
Redis TLS: Set REDIS_TLS=on in your docker-compose.yml for encrypted connections. Use a rediss:// URL scheme.

Remove compose services

When using external databases, remove the postgres and redis services, their volumes, and the depends_on block from your docker-compose.yml. A minimal compose file looks like:

services:
  portal:
    image: managelm/portal:latest
    ports:
      - "3000:3000"
    environment:
      - SERVER_URL=http://localhost:3000
      - JWT_SECRET=your-secret-here
      - SMTP_FROM=noreply@example.com
      - DATABASE_URL=postgresql://user:pass@db-host:5432/managelm
      - REDIS_URL=redis://redis-host:6379
    restart: unless-stopped

Docker Run (without Compose)

If you prefer docker run over Compose:

docker run -d \
  --name managelm \
  -p 3000:3000 \
  -e SERVER_URL=http://localhost:3000 \
  -e JWT_SECRET=$(openssl rand -hex 32) \
  -e SMTP_FROM=noreply@example.com \
  -e POSTGRES_PASSWORD=change-me-strong-password \
  -v managelm_pgdata:/data/postgres \
  -v managelm_redisdata:/data/redis \
  --restart unless-stopped \
  managelm/portal:allinone

Start PostgreSQL and Redis first, then the portal:

# PostgreSQL
docker run -d --name managelm-db \
  -e POSTGRES_DB=managelm \
  -e POSTGRES_USER=managelm \
  -e POSTGRES_PASSWORD=your-db-password \
  -v managelm_pgdata:/var/lib/postgresql/data \
  --restart unless-stopped \
  postgres:16-alpine

# Redis
docker run -d --name managelm-redis \
  -v managelm_redisdata:/data \
  --restart unless-stopped \
  redis:7-alpine redis-server --appendonly yes

# Portal
docker run -d --name managelm \
  -p 3000:3000 \
  -e SERVER_URL=http://localhost:3000 \
  -e JWT_SECRET=$(openssl rand -hex 32) \
  -e SMTP_FROM=noreply@example.com \
  -e DATABASE_URL=postgresql://managelm:your-db-password@managelm-db:5432/managelm \
  -e REDIS_URL=redis://managelm-redis:6379 \
  --link managelm-db --link managelm-redis \
  --restart unless-stopped \
  managelm/portal:latest

First Steps After Install

Once the portal is running:

  1. Set up a reverse proxy with TLS (nginx or Apache) for production. See note below for testing without one.
  2. Register your account at your SERVER_URL.
  3. Configure the LLM — Local (Ollama), Cloud, or Proxied access mode.
  4. Import skills from the built-in catalog.
  5. Install an agent on your first server.
  6. Connect Claude via MCP.
SERVER_URL must match how clients reach the portal. Agents derive their WebSocket URL from this value, so it must be the exact address reachable from agent machines.
  • With a reverse proxy: https://managelm.example.com — the proxy terminates TLS on port 443, so no port is needed.
  • Without a reverse proxy: http://<server-ip>:3000 — include the port since the container listens on 3000. For production, always use a reverse proxy with HTTPS — agents require a trusted TLS connection.
For detailed instructions on all portal features, see the Portal Documentation.

Reverse Proxy

Place a reverse proxy in front of the portal for TLS. WebSocket support is required for agent connections.

nginx

server {
    listen 443 ssl http2;
    server_name managelm.example.com;

    ssl_certificate     /etc/ssl/certs/managelm.pem;
    ssl_certificate_key /etc/ssl/private/managelm.key;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";
        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 $scheme;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

Apache

<VirtualHost *:443>
    ServerName managelm.example.com

    SSLEngine on
    SSLCertificateFile    /etc/ssl/certs/managelm.pem
    SSLCertificateKeyFile /etc/ssl/private/managelm.key

    ProxyPreserveHost On
    ProxyPass        / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/

    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteRule /(.*) ws://127.0.0.1:3000/$1 [P,L]

    ProxyTimeout 86400
</VirtualHost>
Agents stuck connecting? Check that your proxy forwards the Upgrade and Connection headers for WebSocket.

SMTP / Email

The portal sends emails for account verification, password resets, and team invitations. When SMTP_HOST is not set, email sending is disabled entirely — no connection attempts are made.

To enable emails, configure an SMTP relay (Brevo, Mailgun, SendGrid, etc.):

SMTP_HOST=smtp.brevo.com
SMTP_PORT=587
SMTP_SECURE=starttls
SMTP_USER=your-username
SMTP_PASS=your-password

Environment Variables

Required

VariableDescription
SERVER_URL The public URL that browsers and agents use to reach the portal. This must be the externally reachable address — agents derive their WebSocket connection from it.

Behind a reverse proxy (recommended for production): use the proxy's public URL with https://. Do not include the container port — the proxy listens on 443.
Example: https://managelm.example.com

Without a reverse proxy (testing / LAN): use the server IP or hostname with http:// and include the container port.
Example: http://192.168.1.10:3000
JWT_SECRETRandom 32+ char secret. Generate: openssl rand -hex 32
SMTP_FROMSender email address
POSTGRES_PASSWORDPostgreSQL password

Database & Redis

VariableDefaultDescription
DATABASE_URLset by composePostgreSQL connection string. Override for external DB.
REDIS_URLset by composeRedis connection string. Override for external Redis.
DB_SSLnoneSSL mode (none, require, verify, verify-ca)
DB_SSL_CAPath to CA certificate file (used with DB_SSL=verify-ca)
DB_POOL_MAX20Max database connections
REDIS_TLSautoRedis TLS (auto, on, off)
REDIS_DB0Logical database number (0–15). Useful when sharing a Redis instance.

Optional

VariableDefaultDescription
SERVER_PORT3000Portal listen port
DEFAULT_TIMEZONEUTCDefault timezone
SMTP_HOSTSMTP server (empty = email disabled)
SMTP_PORT25SMTP port
SMTP_SECUREnonenone, starttls, tls
SMTP_USER / SMTP_PASSSMTP authentication
DKIM_DOMAINDKIM signing domain
DKIM_SELECTORdefaultDKIM selector
DKIM_PRIVATE_KEY_PATHPath to DKIM key inside container
JWT_ACCESS_EXPIRY1hAccess token lifetime
JWT_REFRESH_EXPIRY7dRefresh token lifetime
TASK_TIMEOUT_SECONDS300Max task duration
TASK_LOG_RETENTION_DAYS30Task log retention
AUDIT_LOG_RETENTION_DAYS90Audit log retention
FILE_TRANSFER_MAX_BYTES26214400Max file transfer size (default 25 MB)
TOS_URLTerms of Service URL (enables ToS checkbox on signup)
SKIP_MIGRATIONSfalseSet to true to skip auto-migrations on startup
LOG_LEVELinfoLog verbosity: trace, debug, info, warn, error, fatal, silent
CLUSTER_WORKERS2Number of Node.js cluster workers. Set to 1 to disable clustering.
NOTIFY_EMAILEmail for platform operator alerts (account created/deleted)
OAUTH_APP_CLIENT_IDOAuth app Client ID for external integrations (OpenAI GPT, etc.). Generate with the command in .env.example.
OAUTH_APP_CLIENT_SECRETOAuth app Client Secret (pair with Client ID above). Users still authenticate individually.

Updating

docker compose pull
docker compose up -d

Migrations run automatically on startup. Back up first for major updates.

Backup & Restore

# Backup
docker compose stop
docker run --rm \
  -v managelm_pgdata:/data/postgres:ro \
  -v managelm_redisdata:/data/redis:ro \
  -v $(pwd):/backup \
  alpine tar czf /backup/managelm-backup-$(date +%F).tar.gz -C / data
docker compose start

# Restore
docker compose down
docker run --rm \
  -v managelm_pgdata:/data/postgres \
  -v managelm_redisdata:/data/redis \
  -v $(pwd):/backup \
  alpine tar xzf /backup/managelm-backup-YYYY-MM-DD.tar.gz -C /
docker compose up -d
# Backup PostgreSQL
docker compose exec postgres pg_dump -U managelm managelm > backup-$(date +%F).sql

# Backup Redis
docker compose exec redis redis-cli BGSAVE
docker cp $(docker compose ps -q redis):/data/dump.rdb ./redis-$(date +%F).rdb

# Restore PostgreSQL
docker compose exec -T postgres psql -U managelm managelm < backup-YYYY-MM-DD.sql

Monitoring

# Health check
curl -s http://localhost:3000/health

# Logs
docker compose logs -f portal

# Last 100 lines
docker compose logs --tail 100 portal

Troubleshooting

Portal won't start

  • Check JWT_SECRET is set (required).
  • Check DATABASE_URL and REDIS_URL are correct.
  • Run docker compose ps — postgres and redis must show "healthy".
  • Check logs: docker compose logs
  • Check for port conflicts on 3000.

Agents can't connect

  • Verify SERVER_URL is reachable from the agent server.
  • Check reverse proxy forwards WebSocket headers. See Reverse Proxy.
  • Test: curl -I https://your-hostname/health

Database permission errors

  • "permission denied for schema public" — Connect as a superuser and run:
    GRANT ALL ON SCHEMA public TO managelm;
  • "permission denied for relation …" — The user needs ownership or full grants. Re-run:
    ALTER DATABASE managelm OWNER TO managelm;
  • "FATAL: password authentication failed" — Check DATABASE_URL credentials match the PostgreSQL user. Test with psql first.
  • "could not connect to server: Connection refused" — Verify listen_addresses in postgresql.conf and check pg_hba.conf allows the portal host.
  • Managed PostgreSQL (AWS RDS, GCP Cloud SQL, etc.) — The managed user is usually not a superuser. Grant schema access explicitly and ensure the security group / firewall allows the portal.

Email not sending

  • Without SMTP_HOST, email sending is disabled. Set it to your SMTP server to enable emails.
  • Check SMTP_FROM in your docker-compose.yml — it must be set.
  • For an SMTP relay, verify SMTP_HOST, SMTP_PORT, and credentials.
  • Check logs: docker compose logs | grep -i mail