Appwrite: Firebase Alternative That Actually Works

I've been locked out of Firebase projects before (billing "surprises", quota limits). Moved to Appwrite - open source, self-hosted, and I actually own my data. The migration took a weekend, and now I have full control over my backend.

Problem

Set up Appwrite on a VPS, created a project, added my frontend domain. When trying to authenticate from the browser, got CORS errors saying "origin not allowed". The web app couldn't make any API calls to Appwrite.

Error: Access to fetch at 'https://appwrite.example.com/v1/account' from origin 'https://myapp.com' has been blocked by CORS policy

What I Tried

Attempt 1: Added my domain to project settings in Appwrite console - didn't work immediately.
Attempt 2: Restarted the Appwrite container - still blocked.
Attempt 3: Tried adding wildcard origin (*) - security risk, and still didn't work.

Actual Fix

Appwrite has two places where CORS needs to be configured: 1) Project settings in the console, and 2) The reverse proxy (Nginx/Caddy) in front of Appwrite. The console change took 5-10 minutes to propagate through their internal cache. Also needed to set _APP_DOMAIN_TARGET correctly.

# 1. In docker-compose.yml, ensure correct domain config:
environment:
  - _APP_DOMAIN=appwrite.yourdomain.com
  - _APP_DOMAIN_TARGET=https://appwrite.yourdomain.com

# 2. In Appwrite Console: Settings → Platform → Add Platform
# - Name: My Web App
# - Domain: https://myapp.com
# - Type: Web App

# 3. If using reverse proxy, add CORS headers:
location /v1 {
    proxy_pass http://appwrite:80;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;

    # CORS headers
    add_header 'Access-Control-Allow-Origin' 'https://myapp.com' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Appwrite-Project' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;

    if ($request_method = 'OPTIONS') {
        return 204;
    }
}

# 4. After making changes, wait 5-10 minutes for Appwrite cache

Problem

Trying to upload video files through Appwrite Storage. Files under 5MB worked fine, but anything larger failed with "file too large" error. Changed project limits to 100MB in console, but the error persisted.

Error: Storage file size limit exceeded (limit: 5242880, actual: 15728640)

What I Tried

Attempt 1: Updated project limits in Appwrite console - no effect.
Attempt 2: Increased bucket limits - still failed at 5MB.
Attempt 3: Tried uploading via REST API directly - same limit.

Actual Fix

The issue was that Nginx (reverse proxy) had its own client_max_body_size limit of 5MB. Appwrite's internal limits were configured correctly, but the request never reached it. Also, PHP-FPM (which Appwrite uses for some endpoints) has its own upload limit.

# Nginx configuration - increase body size
http {
    # Global limit
    client_max_body_size 100M;

    # Or per location
    location /v1/storage {
        client_max_body_size 100M;
        proxy_pass http://appwrite:80;
        # ... other proxy settings
    }
}

# Appwrite docker-compose.yml - adjust PHP limits
environment:
  - _APP_STORAGE_LIMIT=100000000  # 100MB in bytes

# If using PHP-based endpoints, also add:
volumes:
  - ./php-fpm.conf:/usr/local/etc/php-fpm.d/zz-custom.conf

# php-fpm.conf:
php_admin_value[upload_max_filesize] = 100M
php_admin_value[post_max_size] = 100M
php_admin_value[memory_limit] = 256M

# Restart after changes:
docker-compose restart nginx appwrite

Problem

Users were getting logged out every 15-30 minutes. Session was marked as expired in Appwrite, forcing users to re-authenticate constantly. Default session duration should've been 6 months, but something was wrong.

What I Tried

Attempt 1: Increased session duration in project settings - no change.
Attempt 2: Checked JWT tokens - they were valid but Appwrite rejected them.
Attempt 3: Cleared Redis cache - sessions still expired prematurely.

Actual Fix

The issue was that _APP_SESSION_DURATION was set in environment variables, which overrides console settings. Also, Appwrite stores sessions in the database, and the connection was timing out, causing premature session validation failures. Needed to fix the database connection pool.

# In docker-compose.yml, configure session duration:
environment:
  - _APP_SESSION_DURATION=31536000  # 1 year in seconds

# Also check database connection pool settings:
environment:
  - _DB_POOL_MAX=100  # Increase from default
  - _DB_POOL_MIN=10

# For MariaDB/MySQL, ensure wait_timeout is sufficient:
# In MariaDB container config:
[mysqld]
wait_timeout = 31536000
interactive_timeout = 31536000

# Verify session duration via API:
curl https://appwrite.example.com/v1/account/session \
  -H 'X-Appwrite-Project: YOUR_PROJECT_ID' \
  -H 'Authorization: Bearer YOUR_JWT'

# Response should show "expire" timestamp far in future

What I Learned

Production Setup

Complete production setup with all Appwrite services.

# docker-compose.yml for Appwrite
version: '3.8'

services:
  appwrite:
    image: appwrite/appwrite:latest
    container_name: appwrite
    ports:
      - 80:80
      - 443:443
    environment:
      - _APP_DOMAIN=appwrite.yourdomain.com
      - _APP_DOMAIN_TARGET=https://appwrite.yourdomain.com
      - _APP_REDIS_HOST=redis
      - _APP_REDIS_PORT=6379
      - _APP_DB_HOST=mariadb
      - _APP_DB_PORT=3306
      - _APP_DB_USER=root
      - _APP_DB_PASSWORD=your_password
      - _APP_DB_SCHEMA=appwrite
      - _APP_OPENSSL_KEY_V1=your-generated-key
      - _APP_OPENSSL_KEY_V2=your-generated-key
      - _APP_ENCRYPTION_KEY=your-generated-key
      - _APP_SESSION_DURATION=31536000
      - _APP_STORAGE_LIMIT=100000000
    volumes:
      - ./data/appwrite:/app/_storage
      - ./data/config:/app/_config
    depends_on:
      - mariadb
      - redis
    networks:
      - appwrite-network

  mariadb:
    image: mariadb:10
    container_name: appwrite-db
    environment:
      - MYSQL_ROOT_PASSWORD=your_password
      - MYSQL_DATABASE=appwrite
    volumes:
      - ./data/mariadb:/var/lib/mysql
    networks:
      - appwrite-network
    command: --max-allowed-packet=64MB --wait-timeout=31536000

  redis:
    image: redis:alpine
    container_name: appwrite-redis
    volumes:
      - ./data/redis:/data
    networks:
      - appwrite-network

networks:
  appwrite-network:
# Generate secrets
OPENSSL_KEY_V1=$(openssl rand -base64 32)
OPENSSL_KEY_V2=$(openssl rand -base64 32)
ENCRYPTION_KEY=$(openssl rand -base64 32)

# Start services
docker-compose up -d

# Access console at: https://appwrite.yourdomain.com
# Create admin account on first visit

# Useful maintenance commands
docker-compose logs -f appwrite
docker-compose exec appwrite vars
docker-compose exec appwrite migration

Monitoring & Debugging

Keep your Appwrite instance healthy:

Essential Commands

# View Appwrite logs
docker-compose logs -f appwrite

# Check all services status
docker-compose ps

# Access MariaDB console
docker-compose exec mariadb mysql -u root -p appwrite

# Check Redis connectivity
docker-compose exec appwrite redis-cli -h redis ping

# Backup database
docker-compose exec mariadb mysqldump -u root -p appwrite > backup.sql

# Restore database
docker-compose exec -T mariadb mysql -u root -p appwrite < backup.sql

Red Flags to Watch For

Related Resources