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
- Lesson 1: CORS must be configured in both Appwrite console AND reverse proxy - missing either causes failures.
- Lesson 2: File upload limits exist at multiple layers (Nginx, PHP-FPM, Appwrite) - all need adjustment.
- Lesson 3: Environment variables override console settings - check docker-compose.yml first when debugging.
- Overall: Appwrite is powerful but has many moving parts. Understanding how the reverse proxy, Appwrite containers, and database interact is crucial for troubleshooting.
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
- Users logged out frequently - check session duration and database connectivity
- File uploads failing - verify Nginx/PHP/Appwrite limits are all increased
- High memory usage - MariaDB and Redis can be memory hungry, monitor with docker stats
- CORS errors - check both console platform settings and reverse proxy headers