Architektur
Gemeinsame Dienste#
Ein einziger Satz Dienst-Container bedient jedes Projekt auf dem Rechner.
Redis, Memcached und der Mail-Catcher laufen immer. Die Datenbankserver nicht: Jeder startet, sobald ein Projekt auf dem Rechner ihn zum ersten Mal braucht, und bleibt dann an. Ein Rechner, auf dem nur Postgres-Projekte liegen, betreibt nie MariaDB oder MongoDB.
Das ist Zeltros zentrale Designentscheidung, und sie ist ein Kompromiss, kein Gewinn ohne Preis – es lohnt sich, sie zu verstehen, bevor Sie einen Workflow darauf aufbauen.
Was Sie gewinnen. Die meisten Docker-Setups pro Projekt starten einen
Datenbank-Container pro Projekt. Zwanzig laufende Projekte bedeuten zwanzig
Datenbank-Container. Zeltro betreibt einen. Auf einem Entwicklerrechner ist das
oft der Unterschied zwischen „Ich kann alle meine Kunden-Websites laufen lassen“
und „Ich kann drei laufen lassen“. Deshalb braucht die .env eines Projekts auch
keine eigene Datenbank-Verdrahtung: Die Hostnamen unten sind in jedem Projekt
gleich, für immer.
Was Sie aufgeben. Das folgt direkt aus derselben Entscheidung:
- Eine Version pro Engine, rechnerweit. Die gemeinsamen Server laufen mit MariaDB 12, PostgreSQL 17 und MongoDB 8. Ein Projekt, das MySQL 5.7 oder PostgreSQL 15 braucht, bekommt das von ihnen nicht. Legen Sie die Version pro Projekt fest, sind Sie wieder bei einem Container pro Projekt.
- Gemeinsamer Lebenszyklus.
zeltro down <project>stoppt nur dieses Projekt, aberzeltro stop-servicesstoppt die Dienste für alle Projekte, nicht nur für das, an dem Sie gerade gearbeitet haben. - Gemeinsamer Schadensradius. Ein beschädigtes Daten-Volume, eine
außer Kontrolle geratene Migration oder ein
DROP DATABASEtrifft einen Server, den auch alles andere nutzt. Projektdatenbanken sind über ihren Namen getrennt, nicht über eigene Prozesse. - Gemeinsame Zugangsdaten. Jedes Projekt verbindet sich als
root. Das ist ein Werkzeug für die lokale Entwicklung, und die Datenbankserver veröffentlichen keine Ports außerhalb des Docker-Netzwerks (nur die Web-Oberfläche des Mail-Catchers, Port 8025, ist auf dem Host veröffentlicht), aber als Vorbild für die Produktion taugt es nicht.
Wann Zeltro die falsche Wahl ist: wenn Sie Datenbankversionen pro Projekt brauchen oder Ihre lokale Umgebung die Topologie der Produktion exakt abbilden muss. Werkzeuge, die pro Projekt einen kompletten Stack betreiben – DDEV, Lando oder einfaches Docker Compose –, passen für beides besser, um den Preis des oben beschriebenen Ressourcenverbrauchs.
Wann es die richtige ist: wenn Sie viele kleine bis mittlere Projekte auf einem Rechner betreiben und den Speicher lieber den Projekten geben als zwanzig Kopien von MariaDB.
Immer an:
| Dienst | Hostname | Port | Benutzer | Passwort |
|---|---|---|---|---|
| Redis 8 | zeltro-redis |
6379 | — | (keins) |
| Memcached 1.6 | zeltro-memcached |
11211 | — | (keins) |
| Mail-Catcher (Mailpit) | zeltro-mailhog |
SMTP 1025 / UI 8025 | — | (keins) |
Der Mail-Catcher ist Mailpit, ein Ersatz für MailHog. Er behält den Hostnamen
zeltro-mailhog, damit bestehende Projekte nicht geändert werden müssen. Seine
Web-Oberfläche ist außerdem auf dem Host unter http://localhost:8025
veröffentlicht.
Datenbanken, gestartet, wenn ein Projekt eine braucht:
| Dienst | Hostname | Port | Benutzer | Passwort |
|---|---|---|---|---|
| MariaDB 12 (MySQL-kompatibel) | zeltro-mariadb |
3306 | root |
(leer) |
| PostgreSQL 17 | zeltro-postgres |
5432 | root |
password |
| MongoDB 8 | zeltro-mongo |
27017 | root |
password |
zeltro new, clone, setup, install und up ermitteln, welche Datenbank
ein Projekt nutzt, und schalten diesen Server ein, falls er noch nicht läuft. Sie
müssen nichts tun. Um einen von Hand einzuschalten, nutzen Sie
zeltro enable-service mysql, postgres oder mongo.
Nutzen Sie diese Hostnamen und Zugangsdaten direkt, wenn Sie ein Projekt konfigurieren – Sie müssen keine Container untersuchen, um sie herauszufinden. Zeltro schreibt sie automatisch in die .env jedes Projekts.
Optionale gemeinsame Dienste#
Alles außer Redis, Memcached und dem Mail-Catcher ist optional: die drei Datenbanken oben, dazu Objektspeicher, eine Suchmaschine und Web-Oberflächen zur Verwaltung. Sie liegen hinter Docker-Compose-Profilen und bleiben aus, bis ein Rechner sie anfordert, sodass ein Rechner keinen RAM für Dienste bezahlt, die keines seiner Projekte nutzt.
zeltro enable-service minio
zeltro enable-service adminer
zeltro disable-service minio # data volume is kept
zeltro enable-service --help # list every optional service
Einmal eingeschaltet, starten sie bei jedem zeltro up mit und sind aus jedem Projekt-Container über ihren Hostnamen erreichbar, genau wie die Dienste, die immer laufen. Die Liste der eingeschalteten Dienste steht dauerhaft in OPTIONAL_SERVICES in /etc/zeltro-cli/.env.
| Dienst | Slug | Host | Port | Zugangsdaten |
|---|---|---|---|---|
| MinIO-kompatibler S3-Speicher (Silo, der gepflegte MinIO-Fork) | minio |
zeltro-minio |
API 9000 / Konsole 9001 | root / password |
| Meilisearch (Volltextsuche) | meilisearch |
zeltro-meilisearch |
7700 | Master-Key zeltro-dev-master-key |
| phpMyAdmin (MariaDB) | phpmyadmin |
zeltro-phpmyadmin |
80 | root, kein Passwort |
| Adminer (MariaDB, PostgreSQL, SQLite, MongoDB) | adminer |
zeltro-adminer |
8080 | root und das Passwort der jeweiligen Datenbank |
| Mongo Express | mongo-express |
zeltro-mongo-express |
8081 | keine |
| RedisInsight | redisinsight |
zeltro-redisinsight |
5540 | keine |
Die Verwaltungsoberflächen sind bereits mit den gemeinsamen Servern verbunden. RedisInsight zeigt das gemeinsame Redis erst, nachdem Sie beim ersten Start seine Nutzungsbedingungen akzeptiert haben.
Wie ein Projekt erreichbar ist#
Zwei verschiedene Mechanismen, und es lohnt sich, sie auseinanderzuhalten:
- Container → Container: über den Namen. Dockers eingebautes DNS löst Container-Namen im gemeinsamen Netzwerk auf. Code in einem Projekt kann
psql -h zeltro-postgresoderfetch('http://other-project/')ohne weitere Konfiguration nutzen. So teilen sich Projekte Datenbanken und rufen einander auf. - Host oder LAN → Projekt: über die Adresse. Ihr Browser hängt nicht im Docker-Netzwerk, also nutzt er eine Adresse.
zeltro statuszeigt für jedes Projekt zwei an:
| Von | Adresse | Warum |
|---|---|---|
| Dem Rechner, auf dem Zeltro läuft | http://10.x.x.219 |
Die eigene IP des Containers. Kein Port – sonst ist nichts auf dieser Adresse. |
| Einem anderen Gerät im LAN | http://192.168.1.20:219 |
Die IP dieses Rechners plus der veröffentlichte Port des Projekts, also die letzte Zahl seiner Container-IP. |
Unter macOS betreibt Docker Desktop die Container in einer virtuellen Maschine und kann vom Host aus keinen Verkehr an Container-IPs leiten. Deshalb zeigt zeltro status dort http://localhost:<port> als lokale Adresse.
Unter Windows läuft Zeltro in WSL2. Die lokale Adresse funktioniert innerhalb von WSL. In einem Windows-Browser nutzen Sie die LAN-Adresse; das ist die IP der WSL-VM, und sie ändert sich, wenn WSL neu startet.
Zeltro schreibt nicht in /etc/hosts, daher funktioniert http://my-api/ in einem Browser auf dem Host nicht. Nutzen Sie die Adressen, die zeltro status anzeigt. Frühere Versionen haben für jedes Projekt einen Host-Eintrag angelegt; das ist weggefallen, und Anlegen oder Starten eines Projekts braucht dafür kein sudo mehr.
VPC und IP-Vergabe#
Alle Zeltro-Container hängen am Docker-Netzwerk zeltro-cli_vpc (${VPC_SUBNET}.0/24, pro Rechner in /etc/zeltro-cli/.env festgelegt). Der Adressraum ist so aufgeteilt, dass statische IPs nie mit dynamischen kollidieren:
| Bereich | Zweck | Vergabe |
|---|---|---|
.2–.8 |
Dauerhaft laufende Dienste, Datenbanken und phpMyAdmin | Statisch |
.32–.63 |
Hilfs-Container (Worker, Scheduler) | Dynamisch |
.100–.239 |
Einstiegspunkte der Projekte | Statisch, pro Projekt zufällig gewählt |
.250–.254 |
MinIO, Meilisearch, Adminer, Mongo Express, RedisInsight | Statisch |
.240–.249 bleibt als Reserve frei. Der Web-Port jedes Projekts wird auf dem Host unter seinem letzten Oktett veröffentlicht; ein Projekt auf .219 antwortet also auf Port 219.
Wenn Sie eine eigene Compose-Datei schreiben: Geben Sie dem Dienst, der nach außen antwortet, eine statische IP in .100–.239, lassen Sie Hilfsdienste ohne ipv4_address und fassen Sie .2–.8 und .250–.254 nie an.
Basis-Images#
Neue Projekte werden aus einem von drei Images auf Docker Hub unter canebaycomputers/cbc gebaut. Jedes betreibt nginx + supervisor auf Ubuntu Noble. Die Quellen liegen in CaneBayComputers/docker-images, ein Verzeichnis pro Image.
Mit --image <ref> bei new, clone, setup oder install ersetzen Sie das Standard-Image.
cbc:nginx-php8 – PHP-Projekte#
- nginx mit FastCGI zu
php-fpm8.3über einen Unix-Socket; Web-Root/usr/share/nginx/html/public - PHP 8.3 mit
pdo_mysql,pdo_pgsql,pdo_sqlite,redis,mongodb,gd,intl,mbstring,imagick,zip,soap,xdebug, dazuphp-codesnifferundphpmd - Composer, WP-CLI und Node 22 (für Builds mit Vite/npm) sind enthalten
- Supervisor betreibt nginx, php-fpm und einen Laravel-Queue-Worker (4 Prozesse, Autostart)
cbc:nginx-python3 – Python-Projekte#
- nginx als Reverse-Proxy, Port 80 →
localhost:8000, mit WebSocket-Upgrade-Headern - Vorinstalliert:
fastapi,uvicorn,django,flask,gunicorn,python-dotenv,sqlalchemy,psycopg2-binary,PyMySQL,pymongo,redis,pymemcache - Supervisor startet
PYTHON_APP_COMMAND; die App muss an Port 8000 lauschen python3ist verfügbar,pythonnicht
cbc:nginx-node – Node-Projekte#
- nginx als Reverse-Proxy, Port 80 →
localhost:3000, mit WebSocket-Upgrade-Headern - Node 22 LTS
- Supervisor startet
NODE_APP_COMMAND; die App muss an Port 3000 lauschen. Setzen SiePORT=3000in der.envfür Frameworks, die standardmäßig einen anderen Port nutzen.
Projekt-Container werden bei jedem zeltro up aus dem Basis-Image neu erstellt. Alles, was ins Dateisystem eines laufenden Containers installiert wurde, geht verloren – nur das per Bind-Mount eingebundene Projektverzeichnis bleibt. Deshalb steckt die Laufzeit jedes Frameworks fest im Image, und deshalb muss eine SQLite-Datenbank im Projektverzeichnis liegen.
Compose-Anpassung#
Trifft zeltro clone oder zeltro setup auf eine docker-compose.yaml mit mehr als einem Dienst oder einem Image, das nicht von cbc stammt, passt Zeltro sie an:
- Mitgelieferte Dienste
postgres/mysql/mariadb/redis/valkey/mongodbwerden entfernt, und Umgebungsvariablen, die auf sie verweisen, werden auf die gemeinsamen Hostnamen umgeleitet - Der nach außen antwortende Dienst bekommt eine statische VPC-IP und einen
container_name, der zum Projekt passt - Andere Dienste hängen ohne feste IP am Netzwerk
- Das Original bleibt als
docker-compose.upstream.yamlerhalten (nur beim ersten Lauf, damit das echte Original weitere Läufe übersteht) docker-compose.yamlwird in.gitignoreeingetragen, damit die von Zeltro verwaltete Datei nicht in ein gemeinsames Repository zurück committet wird
Der Image-Typ betrifft nur diese Anpassung. Die Framework-Schritte – Installation der Abhängigkeiten, .env-Verdrahtung, Storage-Symlink, Migrationen – richten sich nach der Framework-Erkennung und laufen auch für angepasste Projekte.
Nützliche Optionen: --no-startup, um die angepasste Compose-Datei vor dem Start zu prüfen, --overwrite-env, um die .env einer bestehenden App auf die gemeinsamen Dienste umzuleiten und dabei APP_KEY zu behalten, --db-name <name>, um die Datenbank zu wählen, --no-migration, um Migrationen zu überspringen.
Projektstruktur#
~/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 ist der Standard. zeltro projects-dir zeigt, welches Verzeichnis dieser Rechner nutzt. Das Projektverzeichnis bekommt außerdem eine eigene AGENTS.md mit den Dienstnamen dieses Rechners.
Die Laufzeitkonfiguration liegt in /etc/zeltro-cli/.env. Ein Rechner, der vor der Umbenennung in Zeltro installiert wurde, nutzt womöglich noch ältere Container- und Netzwerknamen; zeltro migrate-names stellt ihn ohne Datenverlust auf die zeltro-*-Namen um.
Einen Fehler in dieser Übersetzung gefunden? Sagen Sie es uns auf GitHub.