How To Deploy Memoh AI Agent Platform with Docker Compose

Deploy Memoh, an open-source multi-bot AI agent system, using Dokploy or Docker Compose. Run isolated AI bots with persistent memory, MCP tool support, and multi-platform channels.

How To Deploy Memoh AI Agent Platform with Docker Compose

If you want AI agents that stay on your hardware and don’t phone home to some SaaS, Memoh is worth a look. It lets you spin up multiple AI bots, each inside its own container, with built-in memory and tool use. Below I cover two ways to deploy it: through Dokploy and with a plain Docker Compose setup.

What is Memoh?

Memoh is an open-source, containerized AI agent system built with Go and Vue 3. You create bots, each running in its own isolated containerd container with persistent memory and access to external tools through MCP (Model Context Protocol). Bots can chat on Telegram, Discord, Lark, Email, or the built-in Web UI, and they remember conversations across sessions.

What Memoh does

FeatureWhat it does
Container isolationEach bot runs inside its own containerd sandbox with a separate filesystem, network, and process tree
Persistent memoryHybrid retrieval using vector search (Qdrant) and keyword search, plus LLM-driven fact extraction
Multi-platform channelsTelegram, Discord, Lark (Feishu), Email, Web, CLI
MCP tool supportBots can browse the web, run commands, edit files, call external tools
Multi-user awarenessBots recognize individual users in group chats and track context per person
Web UIVue 3 dashboard with real-time streaming, a container file manager, and visual config

Key features

  • Create and manage multiple AI bots from a single dashboard
  • Container-level isolation per bot using containerd
  • Hybrid memory engine with dense vector search and BM25 keyword search
  • MCP support for connecting external tools (HTTP, SSE, Stdio)
  • Scheduled tasks and heartbeat-based autonomous actions
  • Works with any OpenAI-compatible, Anthropic, or Google AI provider
  • Role-based access control with ownership transfer
  • Cross-platform identity binding across all channels

Privileged container

The Memoh server container runs in privileged mode because it embeds containerd to manage bot containers. Only deploy this on servers you trust and control.

Architecture overview

Memoh has six services managed by Docker Compose:

ServiceImageRole
postgrespostgres:18-alpineMain database for users, bots, channels, and configuration
qdrantqdrant/qdrant:latestVector database for semantic memory search
migratememohai/server:latestOne-shot service that runs database migrations, then exits
servermemohai/server:latestGo backend with embedded containerd (privileged)
agentmemohai/agent:latestAgent Gateway (Bun/Elysia) for AI chat, tool execution, and SSE streaming
webmemohai/web:latestVue 3 web UI served by Nginx

Startup order: PostgreSQL and Qdrant start first. Once both pass their health checks, the migrate service applies database migrations. The server starts after migration finishes, then the agent gateway and web UI come up last.

Prerequisites

  • A Linux VPS or dedicated server with Docker and Docker Compose v2 installed
  • At least 4 GB RAM (the server runs containerd plus PostgreSQL and Qdrant)
  • Root or sudo access (required for privileged container mode)
  • An API key from an OpenAI-compatible, Anthropic, or Google AI provider
Try Hetzner Cloud Now Try Hostinger VPS

Option 1: Deploy with Dokploy

Dokploy takes care of domains and SSL for you. If you don’t have it set up yet, follow the Dokploy install guide first.

Step 1: Create the config file

Before deploying, you need a config.toml file on your server. SSH into your machine and create it:

mkdir -p /opt/memoh
cat > /opt/memoh/config.toml << 'EOF'
[log]
level = "info"
format = "text"

[server]
addr = "server:8080"

[admin]
username = "admin"
password = "CHANGE_THIS_PASSWORD"
email = "admin@yourdomain.com"

[auth]
jwt_secret = "GENERATE_WITH_openssl_rand_-base64_32"
jwt_expires_in = "168h"

[containerd]
socket_path = "/run/containerd/containerd.sock"
namespace = "default"

[mcp]
image = "memohai/mcp:latest"
snapshotter = "overlayfs"
data_root = "/opt/memoh/data"

[postgres]
host = "postgres"
port = 5432
user = "memoh"
password = "YOUR_DB_PASSWORD"
database = "memoh"
sslmode = "disable"

[qdrant]
base_url = "http://qdrant:6334"
api_key = ""
timeout_seconds = 10

[agent_gateway]
host = "agent"
port = 8081
server_addr = "server:8080"

[web]
host = "127.0.0.1"
port = 8082
EOF

Generate a proper JWT secret:

openssl rand -base64 32

Replace GENERATE_WITH_openssl_rand_-base64_32 and YOUR_DB_PASSWORD with actual values.

Step 2: Create the Dokploy service

  1. Open your Dokploy project
  2. Click Add Service and choose Compose
  3. Name it memoh

Step 3: Paste the compose file

name: "memoh"
services:
  postgres:
    image: postgres:18-alpine
    container_name: memoh-postgres
    environment:
      POSTGRES_DB: memoh
      POSTGRES_USER: memoh
      POSTGRES_PASSWORD: YOUR_DB_PASSWORD
    volumes:
      - postgres_data:/var/lib/postgresql
      - /etc/localtime:/etc/localtime:ro
    expose:
      - "5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U memoh"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - dokploy-network

  qdrant:
    image: qdrant/qdrant:latest
    container_name: memoh-qdrant
    volumes:
      - qdrant_data:/qdrant/storage
    expose:
      - "6333"
      - "6334"
    healthcheck:
      test: ["CMD-SHELL", "timeout 10s bash -c ':> /dev/tcp/127.0.0.1/6333' || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - dokploy-network

  migrate:
    image: memohai/server:latest
    container_name: memoh-migrate
    entrypoint: ["/app/memoh-server", "migrate", "up"]
    volumes:
      - /opt/memoh/config.toml:/app/config.toml:ro
    depends_on:
      postgres:
        condition: service_healthy
    restart: "no"
    networks:
      - dokploy-network

  server:
    image: memohai/server:latest
    container_name: memoh-server
    privileged: true
    pid: host
    volumes:
      - /opt/memoh/config.toml:/app/config.toml:ro
      - containerd_data:/var/lib/containerd
      - server_cni_state:/var/lib/cni
      - memoh_data:/opt/memoh/data
      - /etc/localtime:/etc/localtime:ro
    expose:
      - "8080"
    depends_on:
      migrate:
        condition: service_completed_successfully
      qdrant:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - dokploy-network

  agent:
    image: memohai/agent:latest
    container_name: memoh-agent
    volumes:
      - /opt/memoh/config.toml:/config.toml:ro
      - /etc/localtime:/etc/localtime:ro
    expose:
      - "8081"
    depends_on:
      - server
    restart: unless-stopped
    networks:
      - dokploy-network

  web:
    image: memohai/web:latest
    container_name: memoh-web
    expose:
      - "8082"
    depends_on:
      - server
      - agent
    restart: unless-stopped
    networks:
      - dokploy-network

networks:
  dokploy-network:
    external: true

volumes:
  postgres_data:
  qdrant_data:
  containerd_data:
  memoh_data:
  server_cni_state:

Step 4: Domain and port

Create a domain in Dokploy and map it to the web service on port 8082. After deploying, open https://your-domain.com to access the dashboard.

Notes about the Dokploy setup

  • All services use expose instead of ports since Dokploy handles external routing through its proxy.
  • The config.toml is mounted from /opt/memoh/config.toml on the host. Make sure the database password matches in both the config file and the POSTGRES_PASSWORD environment variable.
  • The server container needs privileged: true and pid: host for containerd to manage bot containers.

Security

Change all default passwords in config.toml before deploying. The default admin password is admin123, so replace it with something strong.

Option 2: Docker Compose (standalone)

This is the standard way to run Memoh on any Linux server with Docker.

Step 1: Create a project directory

mkdir -p /opt/memoh && cd /opt/memoh

Step 2: Create the config file

cat > config.toml << 'EOF'
[log]
level = "info"
format = "text"

[server]
addr = "server:8080"

[admin]
username = "admin"
password = "CHANGE_THIS_PASSWORD"
email = "admin@yourdomain.com"

[auth]
jwt_secret = "GENERATE_WITH_openssl_rand_-base64_32"
jwt_expires_in = "168h"

[containerd]
socket_path = "/run/containerd/containerd.sock"
namespace = "default"

[mcp]
image = "memohai/mcp:latest"
snapshotter = "overlayfs"
data_root = "/opt/memoh/data"

[postgres]
host = "postgres"
port = 5432
user = "memoh"
password = "YOUR_DB_PASSWORD"
database = "memoh"
sslmode = "disable"

[qdrant]
base_url = "http://qdrant:6334"
api_key = ""
timeout_seconds = 10

[agent_gateway]
host = "agent"
port = 8081
server_addr = "server:8080"

[web]
host = "127.0.0.1"
port = 8082
EOF

Generate real values for the secrets:

# Generate JWT secret
openssl rand -base64 32

# Generate database password
openssl rand -base64 16

Update config.toml with the generated values.

Step 3: Create the environment file

cat > .env << 'EOF'
POSTGRES_PASSWORD=YOUR_DB_PASSWORD
MEMOH_CONFIG=./config.toml
EOF

Make sure POSTGRES_PASSWORD matches what you put in config.toml under [postgres] password.

Step 4: Create the compose file

name: "memoh"
services:
  postgres:
    image: postgres:18-alpine
    container_name: memoh-postgres
    environment:
      POSTGRES_DB: memoh
      POSTGRES_USER: memoh
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-memoh123}
    volumes:
      - postgres_data:/var/lib/postgresql
      - /etc/localtime:/etc/localtime:ro
    expose:
      - "5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U memoh"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - memoh-network

  qdrant:
    image: qdrant/qdrant:latest
    container_name: memoh-qdrant
    volumes:
      - qdrant_data:/qdrant/storage
    expose:
      - "6333"
      - "6334"
    healthcheck:
      test: ["CMD-SHELL", "timeout 10s bash -c ':> /dev/tcp/127.0.0.1/6333' || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - memoh-network

  migrate:
    image: memohai/server:latest
    container_name: memoh-migrate
    entrypoint: ["/app/memoh-server", "migrate", "up"]
    volumes:
      - ${MEMOH_CONFIG:-./config.toml}:/app/config.toml:ro
    depends_on:
      postgres:
        condition: service_healthy
    restart: "no"
    networks:
      - memoh-network

  server:
    image: memohai/server:latest
    container_name: memoh-server
    privileged: true
    pid: host
    volumes:
      - ${MEMOH_CONFIG:-./config.toml}:/app/config.toml:ro
      - containerd_data:/var/lib/containerd
      - server_cni_state:/var/lib/cni
      - memoh_data:/opt/memoh/data
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "8080:8080"
    depends_on:
      migrate:
        condition: service_completed_successfully
      qdrant:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - memoh-network

  agent:
    image: memohai/agent:latest
    container_name: memoh-agent
    volumes:
      - ${MEMOH_CONFIG:-./config.toml}:/config.toml:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "8081:8081"
    depends_on:
      - server
    restart: unless-stopped
    networks:
      - memoh-network

  web:
    image: memohai/web:latest
    container_name: memoh-web
    ports:
      - "8082:8082"
    depends_on:
      - server
      - agent
    restart: unless-stopped
    networks:
      - memoh-network

volumes:
  postgres_data:
    driver: local
  qdrant_data:
    driver: local
  containerd_data:
    driver: local
  memoh_data:
    driver: local
  server_cni_state:
    driver: local

networks:
  memoh-network:
    driver: bridge

Step 5: Start the stack

sudo docker compose up -d

First startup takes a couple of minutes while images download and services initialize. Check progress with:

sudo docker compose logs -f

Once everything is up, open http://your-server-ip:8082 in your browser. Log in with the admin credentials you set in config.toml.

After deployment

Log in and add a provider

After logging in, go to Settings > Providers and add your API key for OpenAI, Anthropic, Google, or any compatible endpoint. Bots won’t do anything useful without a provider configured.

Create your first bot

  1. Click Bots in the sidebar
  2. Click Create Bot
  3. Give it a name and select a model from your configured provider
  4. The bot gets its own containerd container automatically

You can now chat with the bot from the Web UI, or connect external channels like Telegram or Discord.

Connect messaging channels

Memoh supports several external channels:

ChannelWhat you need
TelegramA bot token from @BotFather
DiscordA bot application token from the Discord Developer Portal
Lark (Feishu)App ID and App Secret
EmailSMTP credentials or a Mailgun API key

Configure channels from Settings > Channels in the web UI.

Bot memory

Bots remember conversations on their own. Memoh pairs Qdrant for vector-based semantic search with PostgreSQL for structured data and BM25 keyword search. The last 24 hours of context load by default. You can trigger memory compaction and rebuild from the bot settings.

Managing data

All persistent data lives in Docker named volumes:

VolumeContents
postgres_dataPostgreSQL database files
qdrant_dataQdrant vector storage
containerd_dataBot container images and snapshots
memoh_dataBot container data
server_cni_stateCNI network state for container networking

These volumes survive docker compose down. To wipe everything and start fresh:

sudo docker compose down -v

Useful commands

# Check service status
sudo docker compose ps

# View logs for a specific service
sudo docker compose logs -f server

# Restart the stack
sudo docker compose restart

# Update to latest images
sudo docker compose pull && sudo docker compose up -d

The migrate service runs on every startup, so database schema updates are applied automatically when you pull new images.

Production checklist

  • Replace all default passwords in config.toml (admin, JWT secret, PostgreSQL)
  • Set up HTTPS through a reverse proxy (Dokploy handles this, or use Nginx/Caddy)
  • Restrict firewall rules to expose only the ports you need
  • Set memory and CPU limits on containers for stability
  • Back up PostgreSQL and Qdrant volumes regularly
  • Monitor disk usage, because bot containers and vector data grow over time

Privileged mode

The server container runs with privileged: true and pid: host because it embeds containerd. This gives it broad access to the host system. Keep the server behind a firewall and limit SSH access.

FAQ

Why does the server container need privileged mode?

Memoh embeds containerd inside the server container to give each bot its own sandbox. Containerd needs access to Linux kernel features (namespaces, cgroups) that require elevated privileges. There’s no way around this if you want per-bot container isolation.

Can I use Memoh without an AI provider API key?

You can deploy it and poke around the UI, but bots won’t generate any responses until you add at least one provider. Any OpenAI-compatible, Anthropic, or Google endpoint works.

How much RAM does Memoh need?

The base stack (PostgreSQL, Qdrant, server, agent, web) sits around 2-3 GB at idle. Each bot container adds overhead depending on what tools and models it uses. 4 GB is the minimum I’d recommend; go higher if you plan on running several bots at once.

Can I expose only the web UI and keep the API internal?

Yes. In the standalone compose file, remove the ports mapping for the server and agent services. The web UI container communicates with them internally over the Docker network. Only expose port 8082 (or route through a reverse proxy).

How do I update Memoh?

Pull the latest images and restart. The migrate service runs automatically on startup to apply schema changes:

sudo docker compose pull && sudo docker compose up -d

Wrapping up

Memoh is one of those projects that packs a lot into a single Docker Compose stack. You get per-bot container isolation, persistent memory, and multi-platform messaging without stitching together a half-dozen separate tools. The Dokploy route is the fastest if you already use it; otherwise, the standalone compose file works on any Linux box with Docker. From there, everything else happens in the browser.

View Memoh on GitHub