---
title: Self-Hosting
description: Host your own Manacore instance with Docker Compose.
---

import { Steps, Aside, Tabs, TabItem } from '@astrojs/starlight/components';

# Self-Hosting

Run your own Manacore instance using Docker Compose.

## Requirements

- **Docker** 24.0+
- **Docker Compose** v2.0+
- **4GB RAM** minimum (8GB recommended)
- **20GB disk space** minimum
- **Domain name** (optional, for HTTPS)

## Quick Start

<Steps>
1. **Clone the repository**

   ```bash
   git clone https://github.com/manacore/manacore-monorepo.git
   cd manacore-monorepo
   ```

2. **Create environment file**

   ```bash
   cp .env.example .env.production
   ```

   Edit `.env.production` with your settings:

   ```env
   # Database
   POSTGRES_USER=manacore
   POSTGRES_PASSWORD=your-secure-password

   # Redis
   REDIS_PASSWORD=your-redis-password

   # JWT (generate new keys!)
   JWT_PRIVATE_KEY=your-private-key
   JWT_PUBLIC_KEY=your-public-key

   # Domain
   DOMAIN=your-domain.com
   ```

3. **Start services**

   ```bash
   docker compose -f docker-compose.production.yml up -d
   ```

4. **Verify deployment**

   ```bash
   # Check services
   docker compose -f docker-compose.production.yml ps

   # Check health
   curl http://localhost:3001/health
   ```
</Steps>

## Configuration

### docker-compose.production.yml

```yaml
version: '3.8'

services:
  # Database
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./docker/init-db:/docker-entrypoint-initdb.d
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Cache
  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    restart: unless-stopped

  # Object Storage
  minio:
    image: minio/minio
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: ${S3_ACCESS_KEY}
      MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
    volumes:
      - minio_data:/data
    restart: unless-stopped

  # Auth Service
  mana-auth:
    image: ghcr.io/manacore/mana-core-auth:latest
    environment:
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/manacore
      REDIS_HOST: redis
      REDIS_PASSWORD: ${REDIS_PASSWORD}
      JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY}
      JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped

  # Chat Backend
  chat-backend:
    image: ghcr.io/manacore/chat-backend:latest
    environment:
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/chat
      MANA_CORE_AUTH_URL: http://mana-auth:3001
      JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY}
    depends_on:
      - mana-auth
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:
  minio_data:
```

## Generate JWT Keys

Manacore uses EdDSA (Ed25519) for JWT signing:

```bash
# Generate private key
openssl genpkey -algorithm ed25519 -out private.pem

# Extract public key
openssl pkey -in private.pem -pubout -out public.pem

# Convert to base64 for .env
echo "JWT_PRIVATE_KEY=$(cat private.pem | base64 -w 0)"
echo "JWT_PUBLIC_KEY=$(cat public.pem | base64 -w 0)"
```

## Reverse Proxy Setup

### Nginx

```nginx
server {
    listen 80;
    server_name api.your-domain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.your-domain.com;

    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

    location / {
        proxy_pass http://localhost:3001;
        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;
    }
}
```

### Caddy

```
api.your-domain.com {
    reverse_proxy localhost:3001
}

chat-api.your-domain.com {
    reverse_proxy localhost:3002
}
```

## SSL with Let's Encrypt

```bash
# Install certbot
apt install certbot python3-certbot-nginx

# Get certificates
certbot --nginx -d api.your-domain.com -d chat-api.your-domain.com

# Auto-renewal (added automatically)
certbot renew --dry-run
```

## Backup Strategy

### Daily Database Backup

Create `/etc/cron.daily/manacore-backup`:

```bash
#!/bin/bash
BACKUP_DIR=/var/backups/manacore
DATE=$(date +%Y%m%d)

# Create backup directory
mkdir -p $BACKUP_DIR

# Backup all databases
docker exec postgres pg_dumpall -U manacore > $BACKUP_DIR/all_$DATE.sql

# Compress
gzip $BACKUP_DIR/all_$DATE.sql

# Keep last 7 days
find $BACKUP_DIR -name "*.gz" -mtime +7 -delete
```

### Restore from Backup

```bash
# Stop services
docker compose -f docker-compose.production.yml stop

# Restore
gunzip -c /var/backups/manacore/all_20240115.sql.gz | docker exec -i postgres psql -U manacore

# Start services
docker compose -f docker-compose.production.yml start
```

## Updating

<Steps>
1. **Pull latest images**

   ```bash
   docker compose -f docker-compose.production.yml pull
   ```

2. **Backup database** (always!)

   ```bash
   docker exec postgres pg_dumpall -U manacore > backup_before_update.sql
   ```

3. **Restart services**

   ```bash
   docker compose -f docker-compose.production.yml up -d
   ```

4. **Verify**

   ```bash
   docker compose -f docker-compose.production.yml ps
   curl http://localhost:3001/health
   ```
</Steps>

## Troubleshooting

### Services Not Starting

```bash
# Check logs
docker compose -f docker-compose.production.yml logs

# Check specific service
docker compose -f docker-compose.production.yml logs mana-auth
```

### Database Connection Failed

```bash
# Verify postgres is running
docker compose -f docker-compose.production.yml ps postgres

# Check connection
docker exec -it postgres psql -U manacore -c "SELECT 1"
```

### Out of Memory

Increase Docker memory limits or add swap:

```bash
# Add 2GB swap
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
```

<Aside type="caution">
Always test updates on a staging environment before applying to production.
</Aside>
