Skip to content

Docker Deployment

Deploy Gmail Secretary MCP with Docker for persistent email caching and reliable operation.

Prerequisites

  • Docker and Docker Compose installed
  • Google Cloud OAuth credentials (client ID + secret) or Gmail App Password
  • Basic familiarity with YAML configuration

Quick Start

1. Clone and configure:

bash
git clone https://github.com/johnneerdael/gmail-secretary-map.git
cd gmail-secretary-map

# Create config directory
mkdir -p config

# Copy sample config
cp config.sample.yaml config/config.yaml

2. Generate a secure bearer token:

bash
uuidgen
bash
uuidgen  # or: openssl rand -hex 32
powershell
[guid]::NewGuid().ToString()

Add to config/config.yaml:

yaml
bearer_auth:
  enabled: true
  token: "your-generated-uuid-here"

3. Start the container:

bash
docker compose up -d

4. Run authentication setup:

bash
# Option 1: OAuth2 (recommended)
docker exec -it workspace-secretary uv run python -m workspace_secretary.auth_setup \
  --client-id='YOUR_CLIENT_ID.apps.googleusercontent.com' \
  --client-secret='YOUR_CLIENT_SECRET'

# Option 2: App Password (no Google Cloud project needed)
docker exec -it workspace-secretary uv run python -m workspace_secretary.app_password

Simplified Auth (v4.2.2+)

Tokens automatically save to /app/config/token.json. No --token-output flag needed.

5. Monitor sync progress:

bash
docker compose logs -f

You should see:

INFO - Synced 50 new emails from INBOX
INFO - Embedding 50 emails from INBOX

Database Backends

SQLite (Default)

Zero configuration, perfect for single-user deployments:

yaml
database:
  backend: sqlite
  path: /app/config/email_cache.db

Required for semantic search with pgvector:

yaml
database:
  backend: postgres
  host: postgres
  port: 5432
  name: secretary
  user: secretary
  password: ${POSTGRES_PASSWORD}
  
  embeddings:
    enabled: true
    model: text-embedding-3-small
    dimensions: 1536

Docker Compose with PostgreSQL:

yaml
# docker-compose.yml
services:
  workspace-secretary:
    image: ghcr.io/johnneerdael/gmail-secretary-map:latest
    ports:
      - "8000:8000"  # MCP server
      - "8080:8080"  # Web UI
    volumes:
      - ./config:/app/config
    environment:
      - POSTGRES_PASSWORD=your-secure-password
      - OPENAI_API_KEY=sk-...  # For embeddings
      - ENGINE_API_URL=http://127.0.0.1:8001
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    image: pgvector/pgvector:pg16
    environment:
      - POSTGRES_USER=secretary
      - POSTGRES_PASSWORD=your-secure-password
      - POSTGRES_DB=secretary
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U secretary"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

See Semantic Search for embedding configuration details.

Volume Mounts

The container requires a single volume mount:

Host PathContainer PathPurpose
./config//app/config/Configuration, tokens, and cache
yaml
volumes:
  - ./config:/app/config

Your config/ folder contains:

  • config.yaml - Configuration file
  • token.json - OAuth tokens (created by auth setup)
  • email_cache.db - SQLite cache (if using SQLite backend)

Single folder mount only

yaml
# ✅ Correct
volumes:
  - ./config:/app/config

# ❌ Wrong - conflicting mounts
volumes:
  - ./config.yaml:/app/config/config.yaml:ro
  - ./config:/app/config

Sync Behavior

Initial Sync

On first startup:

  1. Connects to Gmail IMAP with CONDSTORE support
  2. Downloads email metadata and bodies in batches
  3. Stores in database (SQLite or PostgreSQL)
  4. Generates embeddings if enabled

Sync times by mailbox size:

EmailsTime
~1,0001-2 minutes
~10,0005-10 minutes
~25,00015-30 minutes

Incremental Sync

After initial sync:

  • IDLE push notifications for real-time updates
  • CONDSTORE for efficient flag change detection
  • UIDNEXT tracking for new message detection
  • Typical incremental sync: < 1 second

Cache Management

Reset the cache:

bash
docker compose stop
rm config/email_cache.db  # SQLite only
docker compose start

View sync stats (SQLite):

bash
docker exec workspace-secretary sqlite3 /app/config/email_cache.db \
  "SELECT folder, COUNT(*) as emails FROM emails GROUP BY folder;"

Authentication

OAuth2 Setup

Run inside the container after it's started:

bash
docker exec -it workspace-secretary uv run python -m workspace_secretary.auth_setup \
  --client-id='YOUR_CLIENT_ID.apps.googleusercontent.com' \
  --client-secret='YOUR_CLIENT_SECRET'

The manual OAuth flow:

  1. Open the printed authorization URL in your browser
  2. Login and approve access
  3. Copy the full redirect URL (even if page doesn't load)
  4. Paste when prompted
  5. Tokens saved automatically to /app/config/token.json

App Password Setup

Alternative without Google Cloud project:

bash
docker exec -it workspace-secretary uv run python -m workspace_secretary.app_password

Enter your Gmail address and App Password when prompted.

Token Refresh

Tokens auto-refresh. If refresh fails, re-run auth setup and restart:

bash
docker exec -it workspace-secretary uv run python -m workspace_secretary.auth_setup \
  --client-id='...' --client-secret='...'
docker compose restart

Environment Variables

VariablePurposeDefault
POSTGRES_PASSWORDPostgreSQL passwordRequired for postgres backend
OPENAI_API_KEYEmbeddings API keyRequired if embeddings enabled
WORKSPACE_TIMEZONEIANA timezoneFrom config.yaml
LOG_LEVELLogging verbosityINFO

Production Recommendations

Resource Limits

yaml
services:
  workspace-secretary:
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M

Restart Policy

yaml
services:
  workspace-secretary:
    restart: always

Log Rotation

yaml
services:
  workspace-secretary:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Connecting Clients

The server exposes a Streamable HTTP endpoint at:

http://localhost:8000/mcp

With bearer auth header:

Authorization: Bearer your-generated-uuid-here

See the Client Setup Guide for Claude Desktop, VS Code, Cursor, and other MCP clients.

Port Configuration

The Docker container exposes three services:

PortServicePurpose
8000MCP ServerAI client connections
8001Engine APIInternal only (not exposed)
8080Web UIHuman interface

Port mapping in docker-compose.yml:

yaml
ports:
  - "8000:8000"  # MCP server
  - "8080:8080"  # Web UI
  # Note: 8001 is internal only, do not expose

Troubleshooting

"Database not initialized" error

Upgrade to v4.2.2+ which fixes this issue.

"Missing client_id or client_secret"

Re-run auth setup with v4.2.1+ which saves credentials in token.json.

PostgreSQL connection refused

Ensure postgres service is healthy before secretary starts:

yaml
depends_on:
  postgres:
    condition: service_healthy

Sync appears stuck

bash
docker compose logs --tail=100 | grep -i error

Common causes:

  • Invalid OAuth tokens (re-run auth setup)
  • Network connectivity issues
  • Gmail rate limiting

High memory during initial sync

Large mailboxes use more memory during sync. Increase container memory limits if needed.


Next: Configure Semantic Search for AI-powered email discovery.

Released under the MIT License.