Appwrite: Guía de supervivencia
Hace unos meses me dio por intentar montar un MVP de una idea tonta que tuve mientras desayunaba. Una aplicación pequeñita, nada del otro mundo: autenticación, una base de datos para guardar cositas, y ya. En lugar de tirar de la API de turno hecha con FastAPI y SQLite (que es lo que suelo hacer cuando quiero tener algo listo en un par de tardes), pensé: "Venga, vamos a probar algo nuevo. Probemos Appwrite."
Error. O acierto. Depende de a quién preguntes.
¿Qué es Appwrite y por qué coño usarlo?
Appwrite es un backend como servicio (BaaS) de código abierto. Básicamente, te da todo lo que necesitas para no tener que escribir el backend de tu aplicación: autenticación, base de datos, almacenamiento de archivos, funciones serverless... Vamos, lo que Firebase debería haber sido si Google no lo hubiese convertido en un vendor lock-in con precios de hipoteca.
La gracia es que, al ser open source (licencia BSD-3), te lo puedes instalar en tu propio servidor. Y digo "instalar" con comillas, porque en realidad es un ecosistema de microservicios en Docker que te va a ocupar media VPS.
El proyecto empezó como un fork de... bueno, Appwrite nació de la cabeza de Eldad Fux en 2019, y desde entonces ha crecido hasta tener una comunidad bastante activa. La versión estable actual (1.5.x cuando escribo esto) ya viene con:
- Autenticación multi-proveedor (email, OAuth, magic links, JWT, etc.)
- Base de datos documental (tipo MongoDB pero más sencillita)
- Almacenamiento de archivos con previews de imágenes
- Funciones serverless en Node.js, Python, PHP, Ruby, Dart...
- Mensajería en tiempo real (WebSockets)
- Consola de administración web bastante apañada
Mi odisea particular montando Appwrite
El deploy: no todo es docker compose up
Lo primero que lees en la documentación oficial: "Run Appwrite with a single command using Docker". Y sí, técnicamente es verdad:
docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.5.6
Pero luego te das cuenta de que ese comando levanta 11 contenedores. Once. Para un backend que podría ser una API de Flask de 200 líneas. Ahí es cuando te planteas seriamente tus decisiones vitales.
Los servicios que levanta son:
| Servicio | Función |
|---|---|
| appwrite-mariadb | Base de datos principal |
| appwrite-redis | Caché y colas |
| appwrite-influxdb | Métricas y telemetría |
| appwrite-traefik | Proxy inverso (por defecto) |
| appwrite-websocket | Conexiones en tiempo real |
| appwrite-functions | Ejecutor de serverless |
| appwrite-maintenance | Tareas programadas |
| appwrite-realtime | Broadcast de eventos |
| appwrite-worker-* | Varios workers (mail, audit, etc.) |
En mi VPS de 4GB de RAM y 2 CPUs, esto me dejaba apenas 800MB libres después de arrancar. Y eso sin contar mi base de datos de MongoDB que ya tenía corriendo para otros proyectos.
Configuración inicial: el baile de las variables de entorno
Appwrite se configura mediante variables de entorno. Y no son pocas:
_APP_ENV=production
_APP_LOCALE=es-ES
_APP_MAINTENANCE_INTERVAL=86400
_APP_OPENSSL_KEY_V1=tu-clave-aqui
_APP_DOMAIN=api.tudominio.com
_APP_DOMAIN_TARGET=api.tudominio.com
_APP_CONSOLE_WHITELIST_ROOT=enabled
_APP_CONSOLE_WHITELIST_EMAILS=tucorreo@dominio.com
_APP_FUNCTIONS_TIMEOUT=900
_APP_FUNCTIONS_BUILD_TIMEOUT=900
_APP_STORAGE_LIMIT=50000000
_APP_USAGE_STATS=enabled
La primera vez que lo configuré, se me olvidó poner _APP_OPENSSL_KEY_V1 y Appwrite se negaba a generar tokens JWT válidos. Estuve dos horas debugueando creyendo que era un problema de mi configuración de Traefik, cuando era una variable de entorno que había pasado por alto. Cosas que pasan cuando lees la documentación en diagonal un domingo por la noche.
Proxy inverso: adiós Traefik, hola Caddy
Appwrite viene con Traefik como proxy inverso por defecto. Traefik está bien, no le voy a hacer feos, pero yo soy más de Caddy. Sobre todo por los certificados SSL automáticos de Let's Encrypt que Caddy maneja de forma trivial.
Mi configuración de Caddy quedó así:
api.tudominio.com {
reverse_proxy appwrite-traefik:80
}
app.tudominio.com {
reverse_proxy appwrite-traefik:80
}
Y luego en el docker-compose.override.yml:
services:
appwrite:
environment:
- _APP_DOMAIN=api.tudominio.com
- _APP_DOMAIN_TARGET=api.tudominio.com
traefik:
ports:
- "127.0.0.1:8080:80"
Así Traefik solo escucha en localhost y Caddy se encarga del SSL y de exponerlo al mundo. Más limpio, más control.
La parte divertida: montando el SDK
Una vez Appwrite está corriendo, el SDK es sorprendentemente limpio. En Python, por ejemplo:
from appwrite.client import Client
from appwrite.services.databases import Databases
from appwrite.services.users import Users
from appwrite.id import ID
client = (
Client()
.set_endpoint("https://api.tudominio.com/v1")
.set_project("mi-projecto-id")
.set_key("mi-api-key-de-servidor")
)
databases = Databases(client)
users = Users(client)
# Crear un usuario
user = users.create(
user_id=ID.unique(),
email="usuario@ejemplo.com",
password="contraseñaSegura123",
name="Usuario de prueba"
)
# Guardar datos
doc = databases.create_document(
database_id="mi-db",
collection_id="mi-coleccion",
document_id=ID.unique(),
data={
"titulo": "Mi primer documento",
"contenido": "Esto es una prueba",
"autor": user["$id"]
}
)
La API es consistente y está bien documentada. Se nota que han puesto cariño en la experiencia de desarrollo. Ojalá pudiera decir lo mismo de la fiabilidad...
Problemas que te vas a encontrar (y cómo solucionarlos)
1. El worker de funciones se come toda la RAM
Si usas funciones serverless con Node.js, prepara la cartera de RAM. El worker de funciones arranca un proceso Node por cada ejecución y a veces no los limpia bien. Solución: limitar el número de workers concurrentes:
_APP_FUNCTIONS_WORKERS_COUNT=2
_APP_FUNCTIONS_TIMEOUT=300
Y poner un restart: unless-stopped con límites de memoria en el docker-compose:
services:
appwrite-functions:
deploy:
resources:
limits:
memory: 512M
2. Redis se llena de sesiones muertas
Appwrite usa Redis para almacenar sesiones y tokens. Si tienes muchos usuarios que no cierran sesión correctamente, Redis se va llenando de basura. Solución: configurar el TTL de sesiones en la consola de Appwrite y ponerle un maxmemory-policy allkeys-lru a Redis.
En el docker-compose override:
services:
appwrite-redis:
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
3. MariaDB: el cuello de botella silencioso
Por defecto, Appwrite configura MariaDB con valores muy conservadores. Si esperas más de unos cientos de usuarios, toca tunearlo:
[mysqld]
innodb_buffer_pool_size = 512M
innodb_log_file_size = 128M
max_connections = 50
query_cache_size = 64M
Esto lo metes en un custom.cnf y lo montas como volumen en el contenedor de MariaDB.
4. Backups: no asumas que Appwrite los hace
Appwrite NO hace backups automáticos. No asumas que porque es una plataforma "todo en uno" va a cuidar de tus datos. Yo tengo un script cutre que hace dump de MariaDB, exporta Redis y comprime InfluxDB, y lo sube todo a un bucket de Backblaze B2 todas las noches:
#!/bin/bash
# backup-appwrite.sh
BACKUP_DIR="/backups/appwrite/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
docker exec appwrite-mariadb mysqldump \
-u root \
-p"${MARIADB_ROOT_PASSWORD}" \
--all-databases \
> "${BACKUP_DIR}/mariadb.sql"
docker exec appwrite-redis redis-cli \
-a "${REDIS_PASSWORD}" \
SAVE \
&& docker cp "appwrite-redis:/data/dump.rdb" "${BACKUP_DIR}/redis.rdb"
tar -czf "${BACKUP_DIR}.tar.gz" "$BACKUP_DIR"
rclone copy "${BACKUP_DIR}.tar.gz" "b2:mis-backups-appwrite/"
echo "Backup completado: $(date)"
Alternativas que consideré (y por qué no las usé)
Supabase
Supabase es el elefante en la habitación del BaaS open source. PostgreSQL, Realtime, autenticación... es muy potente. Pero justamente por eso: necesitas entender PostgreSQL de verdad para sacarle partido. Appwrite es más friendly para desarrolladores que no quieren pensar en SQL (como yo cuando estoy en modo MVP).
PocketBase
PocketBase es tiny, va en un solo binario de Go, y es una pasada lo rápido que arranca. Para proyectos pequeños es ideal. Pero cuando necesitas funciones serverless, almacenamiento de archivos con previews, y toda la parafernalia, se queda corto. Lo uso para prototipos rápidos donde Appwrite sería overkill.
Supabase + edge functions
Estuve a punto de irme por aquí. Deno Deploy, PostgreSQL nativo... pero al final, la experiencia de desarrollo de Appwrite me pareció más coherente para el tipo de proyecto que estaba haciendo. Supabase es más potente, Appwrite es más opinionated y te guía más.
Seguridad: no te fíes del panel bonito
Una de las cosas que más me preocupaba al exponer Appwrite era la seguridad. Al fin y al cabo, es un servicio que maneja autenticación, bases de datos y archivos. Si alguien lo compromete, se lleva todo.
Estas son las medidas que implementé y que te recomiendo:
1. Rate limiting a nivel de proxy
Appwrite tiene rate limiting interno, pero es mejor tener una capa extra. Con Caddy:
api.tudominio.com {
rate_limit {
zone dynamic {
key {remote_host}
events 100
window 1m
}
}
reverse_proxy appwrite:80
}
Esto limita a 100 peticiones por minuto por IP. Suficiente para usuarios legítimos, pero corta de raíz los intentos de fuerza bruta.
2. API keys con permisos mínimos
Appwrite permite crear API keys con permisos específicos. No caigas en la tentación de crear una key con permisos totales para todo. Yo tengo:
- Key de servidor: Acceso total a databases y users (solo para operaciones de backend)
- Key de cliente: Solo lectura a documentos públicos, sin acceso a users
- Key de funciones: Solo para invocar funciones serverless
// En el cliente, usar solo key de cliente
const client = new Client()
.setEndpoint('https://api.tudominio.com/v1')
.setProject('mi-proyecto')
.setKey('client-key-con-permisos-limitados')
3. Aislar el panel de administración
La consola web de Appwrite corre en el mismo dominio que la API. Para mayor seguridad, yo la moví a un subdominio separado y la protegí con autenticación básica de Caddy:
admin.tudominio.com {
basicauth {
cristo $2a$14$hash_de_contraseña
}
reverse_proxy appwrite:80
}
Así, aunque alguien descubra la URL del panel, necesita la contraseña HTTP básica antes de llegar al login de Appwrite.
4. Backups cifrados
Ya mencioné los backups antes, pero vale la pena repetirlo: cifra tus backups antes de subirlos a la nube. Uso GPG:
gpg --encrypt --recipient mi-email backup-appwrite.tar.gz
rclone copy backup-appwrite.tar.gz.gpg b2:mis-backups/
Migración entre versiones: un campo de minas
Appwrite actualiza cada pocos meses, y las migraciones no siempre son sencillas. Pasé de la 1.4 a la 1.5 y el proceso fue:
- Leer las release notes (importante: a veces cambian schemas de BD)
- Hacer backup completo
- Parar todos los servicios
- Actualizar la imagen Docker
- Ejecutar el script de migración
- Verificar que todo funciona
# Mi proceso de actualización
docker compose down
docker pull appwrite/appwrite:1.5.6
docker compose up -d
docker compose exec appwrite appwrite migrate
docker compose logs -f appwrite
La migración de 1.4 a 1.5 fue bien, pero la de 1.3 a 1.4 casi me deja sin datos porque cambiaron el schema de InfluxDB y tuve que migrar manualmente las métricas. Desde entonces, antes de actualizar, hago un dry-run en un contenedor temporal:
# Probar la migración en un contenedor aislado
docker run --rm \
-v ./backup/mariadb.sql:/backup/mariadb.sql \
appwrite/appwrite:1.5.6 \
sh -c "mysql -u root -p$PASSWORD < /backup/mariadb.sql && appwrite migrate"
Comparativa con Firebase (por si alguien lo está considerando)
Firebase es la alternativa comercial más conocida. He usado Firebase en proyectos anteriores (obligado por el trabajo), y aquí van mis impresiones:
| Aspecto | Firebase | Appwrite (self-hosted) |
|---|---|---|
| Coste inicial | Gratis, pero escala a precios de hipoteca | Tu hardware + electricidad |
| Coste a medio plazo | 50-200€/mes para una app con tráfico moderado | ~10€/mes de VPS |
| Vendor lock-in | Total (datos en Google) | Mínimo (datos en tu servidor) |
| Funciones serverless | Cloud Functions (solo GCP) | Cualquier runtime (Node, Python, etc.) |
| Base de datos | Firestore (NoSQL propietario) | Documental (API estándar) |
| Autenticación | Muy completa | Suficiente |
| Tiempo de setup | 10 minutos | 2-4 horas |
| Mantenimiento | Cero (lo gestiona Google) | Tuyo (actualizaciones, backups, seguridad) |
Si estás haciendo un MVP y no sabes si va a funcionar, Firebase te da velocidad inicial. Si sabes que el proyecto va a crecer o te importa la privacidad de los datos, Appwrite self-hosted gana a largo plazo.
Veredicto después de 3 meses
Appwrite no es perfecto. Es pesado, consume recursos que no debería, y la curva de aprendizaje para el self-hosting es más empinada de lo que la documentación admite. Pero:
- Para prototipos y MVPs: Excelente. Te ahorra semanas de escribir boilerplate.
- Para proyectos con pocos usuarios: Bien, si tienes el hardware.
- Para producción a escala: Requiere dedicación. No es instalar y olvidar.
Lo que más valoro de Appwrite es que, al final del día, controlas tus datos. No estás en manos de Google/Firebase ni de los precios de Supabase Cloud. Puedes migrar, exportar, o simplemente apagar el servidor si el proyecto no funciona.
Y eso, en un mundo donde cada vez más servicios intentan encerrarte en su ecosistema, es un lujo que merece la pena pagar.
Si te animas a montarlo, paciencia. Las primeras 48 horas van a ser de prueba y error. Pero una vez que tienes el setup funcionando, es muy gratificante ver tu propia infraestructura BaaS sirviendo peticiones.
Comentarios