La aplicación
¿Qué debería ejecutar?
Qué ejecutar, desde una instalación solo en navegador hasta un despliegue doméstico autoalojado
Esta página es una traducción automática de la documentación en inglés.
Cuatro peldaños. Cada uno añade una funcionalidad y suma algo que ahora tienes que administrar. Empieza por abajo y detente en cuanto tengas lo que necesitas: la mayoría de la gente se queda en el peldaño 0 o 1.
| Peldaño | Obtienes | Administras | Archivo Compose |
|---|---|---|---|
| 0 | Seguimiento de platos + escaneos con IA | Nada | ninguno |
| 1 | Lo mismo, en tu propia máquina | Un contenedor sin estado | docker/compose.yml |
| 2 | Tu diario en dos dispositivos y, en una instancia gestionada, una factura compartida de IA para un hogar u organización | + una base de datos y un secreto | docker/topologies/compose.core.yml |
| 3 | Escaneos en tu propio hardware | + un runtime de inferencia de modelos | docker/topologies/compose.inference.yml |
| 4 | Todo | Todo | docker/topologies/compose.full.yml |
Cada archivo Compose está anotado línea por línea; docker/topologies/README.md es el mismo mapa desde la perspectiva de Compose.
En cada nivel, el servidor de la app también busca nombres de alimentos en la base de datos de alimentos de LowCarbCheck para quienes la usan. Envía nombres, nunca una foto o una entrada del diario. Del nivel 1 en adelante es tu propio servidor el que lo hace: si escanea más de una persona, configúrale una clave gratuita. Consulta architecture.md.
Cada comando que aparece a continuación también se ejecuta con Podman como podman compose. En Ubuntu, ese subcomando necesita tener instalado el paquete podman-compose a su lado. Consulta podman.md.
Peldaño 0: no ejecutar nada
Abre una instancia existente, como https://openplate.lowcarbcheck.org, y pega tu propia clave de proveedor en Ajustes → IA. No hay que registrarse. Tu diario reside en el almacenamiento de ese navegador y nunca llega al servidor de la instancia, por lo que «usar la instancia de otra persona» le da a ese operador mucho menos de lo que parece: consulta architecture.md. Los nombres de los alimentos que escaneas o buscas sí pasan por ella, de camino a la base de datos de alimentos.
En este peldaño no tienes que ejecutar nada. El navegador almacena el diario, el navegador llama al proveedor con la clave que pegaste en él, y el servidor del operador solo envía la página.
Código fuente del diagrama
flowchart LR
host["Someone else's instance"] -->|"HTML and JS"| browser["Your browser"]
browser --- diary["Diary in this browser"]
browser -->|"photo and your key"| cloud["Cloud AI provider"]Ganas: todo el producto, en un minuto, por el coste de tu propio uso de IA. Operas: nada.
La pega real: una instancia pública de demostración no ofrece ninguna garantía de disponibilidad y no tiene copias de seguridad de tus datos. Tu diario está en ese navegador, y borrar los datos del navegador lo borra. Descarga la exportación en JSON desde Perfil → Tus datos con frecuencia, o pasa al nivel 1.
Nivel 1: la aplicación en tu propia máquina
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/compose.yml
docker compose -f compose.yml up -dÁbrelo comohttp://localhost:3000en esa máquina, o sobre HTTPS. Desde otro dispositivo,http://<its address>:3000muestra el diario. La instalación de la app, el uso sin conexión y la conexión a OpenRouter en un clic no funcionan ahí. Consulta self-hosting.md.
El nivel 1 cambia un solo bloque. La página se sirve desde un contenedor que tú ejecutas, y la ruta de la foto es exactamente la de arriba.
Código fuente del diagrama
flowchart LR
app["openplate app, your box"] -->|"HTML and JS"| browser["Your browser"]
browser --- diary["Diary in this browser"]
browser -->|"photo and your key"| cloud["Cloud AI provider"]Ganas: la aplicación en hardware que tú controlas, actualizable cuando tú decidas, sin depender de la instancia de nadie más. Operas: un solo contenedor. Sin base de datos, sin paso de .env, sin secretos que generar y sin nada que migrar al actualizar. Si se cae, no se pierde nada, porque no almacena nada. Archivo Compose: docker/compose.yml.
Este es el punto en el que recomendamos quedarse. Todo lo que viene a continuación añade trabajo operativo real.
La guía detallada completa está en self-hosting.md. Explica HTTPS, necesario para instalar la PWA, conectar con OpenRouter en un clic e iniciar sesión a partir del nivel 2.
Nivel 2: añadir sincronización
Ganas: un solo diario entre tus dispositivos. La forma honesta de plantear esto es una persona, dos dispositivos: un teléfono y un portátil que se mantienen sincronizados. El uso en familias es el segundo, y resulta menos adecuado: la sincronización es por cuenta, así que dos personas que comparten cuenta comparten un solo diario en vez de tener uno cada una. Dos personas que quieran diarios separados necesitan dos cuentas, o simplemente dos dispositivos en el nivel 1 sin sincronización alguna.
El nivel 2 añade un segundo servidor y una base de datos detrás. Cada dispositivo envía el mismo bloque cifrado y descarga el del otro, y la foto sigue saliendo de cada dispositivo hacia el proveedor.
Código fuente del diagrama
flowchart LR
app["openplate app"] -->|"HTML and JS"| phone["Phone"]
app -->|"HTML and JS"| laptop["Laptop"]
phone -->|"ciphertext"| sync["openplate-core"]
laptop -->|"ciphertext"| sync
sync --> db[("Postgres")]
phone -->|"photo and your key"| cloud["Cloud AI provider"]
laptop -->|"photo and your key"| cloudOperas: la aplicación, un servicio de cuentas y un Postgres. Esto supone un salto importante: un servicio de cuentas tiene una base de datos que conviene respaldar, un SERVER_SECRET que conservar y usuarios que pueden quedarse sin acceso. Lee el README de openplate-core antes de exponerlo a internet. Archivo Compose: docker/topologies/compose.core.yml.
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.core.yml
echo "SERVER_SECRET=$(openssl rand -hex 32)" >> .env
echo "ADMIN_TOKEN=$(openssl rand -hex 32)" >> .env
echo "PUBLIC_APP_URL=https://openplate.example.com" >> .env
echo "PUBLIC_SYNC_URL=https://sync.example.com" >> .env
echo "TRUST_PROXY=1" >> .env # 1 behind one reverse proxy, 0 with none
docker compose -f compose.core.yml up -dLas cuentas necesitan una página segura. Iniciar sesión, registrarse y abrir una invitación fallan enhttp://<LAN address>sin cifrar. Usa HTTPS, con un nombre de dominio o en una red doméstica sin él, olocalhostmediante un túnel ssh para una prueba. Nadie se registra por su cuenta. Creas la primera invitación en el servidor conADMIN_TOKEN, como muestra Crea la primera cuenta. Una red doméstica sin nombre de dominio obtiene HTTPS de Caddy con un certificado local.
El servicio guarda cada entrada como texto cifrado y nunca recibe tu contraseña. Sí conserva el código de recuperación de cada cuenta, sellado bajo su propio secreto, de modo que una contraseña olvidada se restablece mediante un enlace (enviado por correo, o entregado por ti en una instancia sin correo) y el diario vuelve a estar accesible. Eso también implica que el operador del servicio puede, en principio, abrir un diario alojado en él. sync.md expone este compromiso al detalle, al igual que la app antes de terminar de configurar la sincronización.
El servidor también puede cubrir una factura compartida de IA, si lo activas. Configura INSTANCE_MODE=managed y la instancia pasa a ser una que un administrador gestiona para un hogar o una organización: los administradores invitan a personas desde /admin (o con la API de administración), asignan una cuota diaria a cada cuenta y cada escaneo con sesión iniciada pasa por el propio proxy de IA del servidor central: sin servicios independientes ni enlaces de invitación aparte. El correo es opcional: /admin muestra siempre la invitación como un enlace que un administrador puede copiar y enviar, se mande por correo o no. Consulta configuration.md#managed-instances y family-setup.md para ver cuándo conviene activar esto en lugar de usar subclaves del proveedor.
En una instancia administrada, ese mismo servidor gestiona también el escaneo. Un miembro con la sesión iniciada envía la foto al proxy de IA, el servidor la descuenta de la cuota diaria de esa cuenta y reenvía la petición al destino que haya configurado el operador.
Código fuente del diagrama
flowchart LR
browser["Member's browser"] -->|"ciphertext"| sync["openplate-core, managed"]
browser -->|"photo"| sync
sync --- quota["Daily allowance per account"]
sync -->|"photo"| upstream["Cloud provider, or inference"]Permite que los miembros se inviten entre sí, pero mantén un límite bajo. En una instancia gestionada, define MEMBER_INVITE_DAILY_AI_LIMIT y MEMBER_INVITE_ALLOWANCE_DAYS en el servidor central. Esto permite que un miembro ordinario invite a alguien sin pedirte permiso antes. Si tú pagas la clave del proveedor, define también MEMBER_INVITE_LIFETIME_CAP=2. El valor predeterminado es 5, adecuado para una instancia donde se comparte la factura de la IA. Fijarlo en 2 basta para una pareja y un amigo, y mantiene el crecimiento a un ritmo fácil de supervisar. Consulta configuration.md#member-invites.
Peldaño 3: añade inferencia autoalojada
El peldaño 3 ejecuta el escaneo en tu propio hardware. El navegador envía las fotos directamente al contenedor de inferencia. Tus navegadores deben resolver la dirección de ese contenedor. El modelo identifica cada alimento y calcula su peso en gramos. openplate-inference consulta los macronutrientes en la fuente de alimentos que configures.
Código fuente del diagrama
flowchart LR
app["openplate app"] -->|"HTML and JS"| browser["Your browser"]
browser --- diary["Diary in this browser"]
browser -->|"photo, browser reachable address"| inf["openplate-inference"]
inf --- weights["Model runtime and weights"]
inf --- usda["Configured food data, USDA by default"]Ganas: escaneos locales de platos sin cuenta de IA en la nube, tarifas por escaneo ni tráfico saliente de fotos. La imagen del contenedor incluye por defecto un extracto de USDA FoodData Central, por lo que openplate-inference consulta los macronutrientes en lugar de inventarlos. Operas: un runtime del modelo y unos pocos gigabytes de pesos, más lo necesario para que el endpoint sea accesible desde tus navegadores (la foto viaja del dispositivo al endpoint, así que un nombre de host de compose no funciona aquí). Archivo Compose: docker/topologies/compose.inference.yml.
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.inference.yml
echo "INFERENCE_API_KEY=opk_$(openssl rand -hex 24)" >> .env
echo "PUBLIC_APP_URL=http://192.168.1.20:3000" >> .env
echo "PUBLIC_INFERENCE_URL=http://192.168.1.20:8300/v1" >> .env
echo "TRUST_PROXY=0" >> .env # 0 with no reverse proxy, 1 behind one
docker compose -f compose.inference.yml up -dSustituye 192.168.1.20 por la dirección de tu servidor. Una vez que la aplicación esté en HTTPS detrás de un proxy inverso, la dirección de inferencia también debe usar https://, y TRUST_PROXY debe establecerse en 1. self-hosting.md lo explica paso a paso.
Este peldaño está pensado para dos perfiles:
- Eres dueño del hardware. Una máquina con GPU, o una con una CPU razonablemente potente.
- Ya utilizas un runtime de inferencia. Si ya tienes en marcha llama.cpp, Ollama o vLLM con GPU, define
MODEL_PROFILE=externalyMODEL_RUNTIME_URL: openplate-inference no descargará nada ni iniciará un segundo modelo, sino que usará lo que ya tienes. Revisa primero la matriz de compatibilidad; la compilación CPU de vLLM no puede ejecutar esto.
Sinceridad sobre el hardware. El perfil pequeño lite pesa 2.0 GiB y requiere 8+ núcleos modernos con AVX2 y 4 GB de RAM libre en un equipo solo con CPU; el perfil más grande quality pesa 5.8 GiB y exige un mínimo de 5.8 GiB de VRAM. Los escaneos en CPU tardan de segundos a minutos, y el rendimiento no mejora con la concurrencia: planifica la capacidad como si la máquina trabajara en serie. Las cifras medidas para cada perfil están en docs/hardware.md de openplate-inference. Léelo antes de comprar nada.
Una vez en ejecución, puedes entregar una clave a cada persona (Ajustes → IA → Compatible con OpenAI) o configurar DEFAULT_INFERENCE_BASE_URL y sus variables asociadas para que cualquier visitante se conecte con un toque, teniendo en cuenta que DEFAULT_INFERENCE_API_KEY queda incrustada en la página y cualquiera que pueda abrir la app puede leerla. Consulta configuration.md.
El servidor central y la inferencia son capas distintas
Son fáciles de confundir y se combinan entre sí.
- openplate-inference es la capa de cómputo. Responde a la pregunta qué hay en este plato. Incluye un runtime de modelo y pesos, y necesita hardware.
- openplate-core, en una instancia gestionada, es la capa de gestión de usuarios. Responde a quién tiene permiso para gastar, cuánto y cómo se lo retiro. No incluye ningún modelo y lo reenvía todo.
Apunta el proxy de IA de una instancia gestionada a tu máquina de inferencia (UPSTREAM_BASE_URL de openplate-core, con uno de los API_KEYS del servicio de inferencia como UPSTREAM_API_KEY) y obtendrás ambas cosas: escaneos en tu propio hardware, con cuotas por cuenta por delante. Si en su lugar lo apuntas a un proveedor en la nube, obtendrás gasto compartido sin necesidad de hardware. En cualquier caso, el mismo servidor central gestiona también el diario: la sincronización y el proxy de IA son ahora un único servicio, no dos (architecture.md).
Peldaño 4: todo
El peldaño 4 une los dos peldaños anteriores. No introduce nada nuevo.
Código fuente del diagrama
flowchart LR
app["openplate app"] -->|"HTML and JS"| phone["Phone"]
app -->|"HTML and JS"| laptop["Laptop"]
phone -->|"ciphertext"| sync["openplate-core"]
laptop -->|"ciphertext"| sync
sync --> db[("Postgres")]
phone -->|"photo"| inf["openplate-inference"]
laptop -->|"photo"| infGanas: el peldaño 2 y el peldaño 3 juntos (tu diario en todos los dispositivos, escaneado en tu propio hardware y sin enviar nada a terceros). Operas: todo. La app, el servidor central, Postgres, el runtime del modelo y direcciones accesibles desde el navegador para dos de ellos. Archivo Compose: docker/topologies/compose.full.yml. Su encabezado lista las líneas de .env: las del peldaño 2 y el peldaño 3 juntas.
Podman ejecuta esto de la misma manera: podman compose -f compose.full.yml up -d.
No hay nada nuevo que aprender en este peldaño. Es la unión de los dos anteriores, con el mismo SERVER_SECRET, la misma obligación de hacer copias de seguridad y los mismos requisitos mínimos de hardware.
Compartir una factura en lugar de un servidor
Si tu motivo para subir esta escalera era «en mi casa hace falta más de una clave de IA», la primera respuesta ni siquiera es un peldaño. Se resuelve en el proveedor, con claves individuales y límites de gasto por persona, y no requiere software adicional. En family-setup.md tienes los pasos y, si tu proveedor no emite subclaves con límite, un servidor central gestionado como alternativa (peldaño 2, con INSTANCE_MODE=managed).