Arquitectura
Servicios compartidos#
Un solo conjunto de contenedores de servicios atiende a todos los proyectos del equipo.
Redis, Memcached y el capturador de correo siempre están en ejecución. Los servidores de bases de datos no: cada uno arranca la primera vez que un proyecto del equipo lo necesita, y a partir de ahí sigue encendido. Un equipo que solo tiene proyectos con Postgres nunca ejecuta MariaDB ni MongoDB.
Esta es la decisión de diseño central de Zeltro, y es un compromiso, no una ventaja gratis: conviene entenderla antes de montar un flujo de trabajo sobre ella.
Lo que ganas. La mayoría de las configuraciones de Docker por proyecto arrancan
un contenedor de base de datos por proyecto. Veinte proyectos en marcha son veinte
contenedores de base de datos. Zeltro ejecuta uno. En una estación de trabajo de
desarrollo, eso suele marcar la diferencia entre "puedo tener levantados todos los
sitios de mis clientes" y "puedo tener tres". También es la razón por la que el
.env de un proyecto no necesita ninguna fontanería de base de datos propia: los
nombres de host de abajo son los mismos en todos los proyectos, siempre.
Lo que cedes. Esto se deriva directamente de la misma decisión:
- Una sola versión de cada motor para todo el equipo. Los servidores compartidos ejecutan MariaDB 12, PostgreSQL 17 y MongoDB 8. Un proyecto que necesite MySQL 5.7 o PostgreSQL 15 no puede obtenerlo de ellos. Si fijas la versión por proyecto, vuelves a tener un contenedor por proyecto.
- Ciclo de vida compartido.
zeltro down <project>detiene solo ese proyecto, perozeltro stop-servicesdetiene los servicios de todos los proyectos, no solo del que estabas trabajando. - Radio de impacto compartido. Un volumen de datos dañado, una migración
descontrolada o un
DROP DATABASEafectan a un servidor que todo lo demás también usa. Las bases de datos de los proyectos están aisladas por nombre, no por proceso. - Credenciales compartidas. Todos los proyectos se conectan como
root. Es una herramienta de desarrollo local y los servidores de bases de datos no publican ningún puerto fuera de la red de Docker (solo se publica en el host la interfaz web del capturador de correo, en el puerto 8025), pero no es un modelo para copiar en producción.
Cuándo Zeltro no es la opción adecuada: necesitas versiones de base de datos por proyecto, o necesitas que tu entorno local refleje exactamente la topología de producción. Las herramientas que ejecutan un stack completo por proyecto (DDEV, Lando o Docker Compose a secas) encajan mejor en ambos casos, a costa del consumo de recursos descrito arriba.
Cuándo sí lo es: ejecutas muchos proyectos pequeños o medianos en un mismo equipo y prefieres gastar esa memoria en los proyectos y no en veinte copias de MariaDB.
Siempre en ejecución:
| Servicio | Nombre de host | Puerto | Usuario | Contraseña |
|---|---|---|---|---|
| Redis 8 | zeltro-redis |
6379 | — | (ninguna) |
| Memcached 1.6 | zeltro-memcached |
11211 | — | (ninguna) |
| Capturador de correo (Mailpit) | zeltro-mailhog |
SMTP 1025 / UI 8025 | — | (ninguna) |
El capturador de correo es Mailpit, un reemplazo de MailHog. Conserva el nombre de
host zeltro-mailhog para que los proyectos existentes no tengan que cambiar. Su
interfaz web también se publica en el host, en http://localhost:8025.
Bases de datos, que arrancan cuando un proyecto necesita una:
| Servicio | Nombre de host | Puerto | Usuario | Contraseña |
|---|---|---|---|---|
| MariaDB 12 (compatible con MySQL) | zeltro-mariadb |
3306 | root |
(vacía) |
| PostgreSQL 17 | zeltro-postgres |
5432 | root |
password |
| MongoDB 8 | zeltro-mongo |
27017 | root |
password |
zeltro new, clone, setup, install y up averiguan qué base de datos usa un
proyecto y activan ese servidor si todavía no está en ejecución. No tienes que hacer
nada. Para activar uno a mano, usa zeltro enable-service mysql, postgres o
mongo.
Usa estos nombres de host y credenciales directamente al configurar un proyecto: no hace falta inspeccionar los contenedores para descubrirlos. Zeltro los escribe automáticamente en el .env de cada proyecto.
Servicios compartidos opcionales#
Todo salvo Redis, Memcached y el capturador de correo es opcional: las tres bases de datos de arriba, además de almacenamiento de objetos, un motor de búsqueda y paneles web de administración. Están detrás de perfiles de Docker Compose y permanecen apagados hasta que un equipo los pide, para que un equipo no gaste RAM en servicios que ninguno de sus proyectos usa.
zeltro enable-service minio
zeltro enable-service adminer
zeltro disable-service minio # data volume is kept
zeltro enable-service --help # list every optional service
Una vez activados, arrancan con cada zeltro up y se resuelven por nombre de host desde dentro de cualquier contenedor de proyecto, igual que los servicios que siempre están en ejecución. La lista de servicios activados se guarda en OPTIONAL_SERVICES, en /etc/zeltro-cli/.env.
| Servicio | Slug | Host | Puerto | Credenciales |
|---|---|---|---|---|
| Almacenamiento S3 compatible con MinIO (Silo, el fork mantenido de MinIO) | minio |
zeltro-minio |
API 9000 / consola 9001 | root / password |
| Meilisearch (búsqueda de texto completo) | meilisearch |
zeltro-meilisearch |
7700 | clave maestra zeltro-dev-master-key |
| phpMyAdmin (MariaDB) | phpmyadmin |
zeltro-phpmyadmin |
80 | root, sin contraseña |
| Adminer (MariaDB, PostgreSQL, SQLite, MongoDB) | adminer |
zeltro-adminer |
8080 | root y la contraseña de esa base de datos |
| Mongo Express | mongo-express |
zeltro-mongo-express |
8081 | ninguna |
| RedisInsight | redisinsight |
zeltro-redisinsight |
5540 | ninguna |
Los paneles de administración vienen ya conectados a los servidores compartidos. RedisInsight muestra el Redis compartido solo después de que aceptes la pantalla de condiciones de su primer inicio.
Cómo se llega a un proyecto#
Son dos mecanismos distintos, y conviene no mezclarlos:
- De contenedor a contenedor: por nombre. El DNS integrado de Docker resuelve los nombres de los contenedores en la red compartida. El código dentro de un proyecto puede hacer
psql -h zeltro-postgresofetch('http://other-project/')sin configuración adicional. Así es como los proyectos comparten bases de datos y se llaman entre sí. - Del host o la LAN al proyecto: por dirección. Tu navegador no está en la red de Docker, así que usa una dirección.
zeltro statusmuestra dos para cada proyecto:
| Desde | Dirección | Por qué |
|---|---|---|
| El equipo que ejecuta Zeltro | http://10.x.x.219 |
La IP propia del contenedor. Sin puerto: no hay nada más en esa dirección. |
| Otro dispositivo de la LAN | http://192.168.1.20:219 |
La IP de este equipo más el puerto publicado del proyecto, que es el último número de la IP de su contenedor. |
En macOS, Docker Desktop ejecuta los contenedores dentro de una máquina virtual y no puede enrutar tráfico desde el host hacia las IP de los contenedores, así que zeltro status muestra http://localhost:<port> como dirección local.
En Windows, Zeltro se ejecuta dentro de WSL2. La dirección local funciona desde dentro de WSL. Desde un navegador de Windows, usa la dirección LAN, que es la IP de la máquina virtual de WSL y cambia cuando WSL se reinicia.
Zeltro no escribe en /etc/hosts, así que http://my-api/ no funciona en un navegador del host. Usa las direcciones que muestra zeltro status. Las versiones anteriores agregaban una entrada de host por cada proyecto; eso ya no existe, y crear o arrancar un proyecto ya no necesita sudo para ello.
VPC y asignación de IP#
Todos los contenedores de Zeltro se conectan a la red de Docker zeltro-cli_vpc (${VPC_SUBNET}.0/24, definida por equipo en /etc/zeltro-cli/.env). El espacio de direcciones está dividido para que las IP estáticas nunca choquen con las dinámicas:
| Rango | Uso | Asignación |
|---|---|---|
.2–.8 |
Servicios siempre en ejecución, bases de datos y phpMyAdmin | Estática |
.32–.63 |
Contenedores auxiliares (workers, programadores de tareas) | Dinámica |
.100–.239 |
Puntos de entrada de los proyectos | Estática, elegida al azar para cada proyecto |
.250–.254 |
MinIO, Meilisearch, Adminer, Mongo Express, RedisInsight | Estática |
.240–.249 se deja libre como margen. El puerto web de cada proyecto se publica en el host con su último octeto, así que un proyecto en .219 responde en el puerto 219.
Si escribes un compose propio: dale al servicio web una IP estática en .100–.239, deja los auxiliares sin ipv4_address y nunca toques .2–.8 ni .250–.254.
Imágenes base#
Los proyectos nuevos se construyen a partir de una de tres imágenes de Docker Hub bajo canebaycomputers/cbc. Cada una ejecuta nginx + supervisor sobre Ubuntu Noble. El código fuente está en CaneBayComputers/docker-images, con un directorio por imagen.
Cambia la imagen predeterminada con --image <ref> en new, clone, setup o install.
cbc:nginx-php8: proyectos PHP#
- nginx con FastCGI hacia
php-fpm8.3por un socket Unix; raíz web/usr/share/nginx/html/public - PHP 8.3 con
pdo_mysql,pdo_pgsql,pdo_sqlite,redis,mongodb,gd,intl,mbstring,imagick,zip,soap,xdebug, además dephp-codesnifferyphpmd - Incluye Composer, WP-CLI y Node 22 (para compilaciones con Vite/npm)
- Supervisor ejecuta nginx, php-fpm y un worker de colas de Laravel (4 procesos, arranque automático)
cbc:nginx-python3: proyectos Python#
- Proxy inverso nginx, puerto 80 →
localhost:8000, con cabeceras de actualización a WebSocket - Preinstalado:
fastapi,uvicorn,django,flask,gunicorn,python-dotenv,sqlalchemy,psycopg2-binary,PyMySQL,pymongo,redis,pymemcache - Supervisor ejecuta
PYTHON_APP_COMMAND; la app tiene que escuchar en el puerto 8000 - Está disponible
python3;pythonno
cbc:nginx-node: proyectos Node#
- Proxy inverso nginx, puerto 80 →
localhost:3000, con cabeceras de actualización a WebSocket - Node 22 LTS
- Supervisor ejecuta
NODE_APP_COMMAND; la app tiene que escuchar en el puerto 3000. DefinePORT=3000en.envpara los frameworks que usan otro puerto de forma predeterminada.
Los contenedores de los proyectos se recrean a partir de la imagen base con cada zeltro up. Todo lo que se instale en el sistema de archivos de un contenedor en ejecución se pierde: solo sobrevive el directorio del proyecto, que está montado desde el host. Por eso el entorno de ejecución de cada framework viene incluido en la imagen, y por eso una base de datos SQLite tiene que estar en el directorio del proyecto.
Adaptación del compose#
Cuando zeltro clone o zeltro setup encuentran un docker-compose.yaml con más de un servicio o con una imagen que no es cbc, Zeltro lo adapta:
- Se quitan los servicios empaquetados
postgres/mysql/mariadb/redis/valkey/mongodb, y las variables de entorno que apuntaban a ellos se redirigen a los nombres de host compartidos - El servicio web recibe una IP estática de la VPC y un
container_nameque coincide con el proyecto - Los demás servicios se conectan a la red sin una IP fija
- El original se guarda como
docker-compose.upstream.yaml(solo la primera vez, para que el verdadero original sobreviva a las siguientes ejecuciones) docker-compose.yamlse agrega a.gitignore, para que el archivo gestionado por Zeltro no se suba de vuelta a un repositorio compartido
El tipo de imagen solo afecta a esta adaptación. Los pasos del framework (instalación de dependencias, configuración del .env, enlace simbólico de storage, migraciones) dependen de la detección del framework y también se ejecutan en los proyectos adaptados.
Opciones útiles: --no-startup para revisar el compose adaptado antes de que arranque, --overwrite-env para redirigir el .env de una app existente a los servicios compartidos conservando APP_KEY, --db-name <name> para elegir la base de datos y --no-migration para omitir las migraciones.
Estructura de un proyecto#
~/zeltro-projects/
├── my-api/
│ ├── docker-compose.yaml # Zeltro-managed (gitignored)
│ ├── docker-compose.upstream.yaml # the original, if there was one
│ ├── AGENTS.md # hand-off context for AI agents
│ ├── .env
│ └── [your project files]
└── my-shop/
~/zeltro-projects es el valor predeterminado. zeltro projects-dir muestra el que usa este equipo. El directorio de proyectos también tiene su propio AGENTS.md con los nombres de los servicios de este equipo.
La configuración de ejecución está en /etc/zeltro-cli/.env. Un equipo instalado antes del cambio de nombre a Zeltro puede seguir usando nombres antiguos de contenedores y redes; zeltro migrate-names lo pasa a los nombres zeltro-* sin perder datos.
¿Encontraste un error en esta traducción? Avísanos en GitHub.