Skip to content

Self-Hosting

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

  1. Clone the repository

    Terminal window
    git clone https://github.com/manacore/manacore-monorepo.git
    cd manacore-monorepo
  2. Create environment file

    Terminal window
    cp .env.example .env.production

    Edit .env.production with your settings:

    # 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

    Terminal window
    docker compose -f docker-compose.production.yml up -d
  4. Verify deployment

    Terminal window
    # Check services
    docker compose -f docker-compose.production.yml ps
    # Check health
    curl http://localhost:3001/health

Configuration

docker-compose.production.yml

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:

Terminal window
# 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

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

Terminal window
# 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:

#!/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

Terminal window
# 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

  1. Pull latest images

    Terminal window
    docker compose -f docker-compose.production.yml pull
  2. Backup database (always!)

    Terminal window
    docker exec postgres pg_dumpall -U manacore > backup_before_update.sql
  3. Restart services

    Terminal window
    docker compose -f docker-compose.production.yml up -d
  4. Verify

    Terminal window
    docker compose -f docker-compose.production.yml ps
    curl http://localhost:3001/health

Troubleshooting

Services Not Starting

Terminal window
# 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

Terminal window
# 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:

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