Jedes Projekt ist eine Gruppe im GitLab von MainPath unter gitlab.application-platform.com mit einem Repository pro Komponente, local-configuration für die gemeinsame Editor-Konfiguration, gitops-configuration für das Deployment und gitlab-profile mit der README.
orgs/<organisation>/<projekt>/
├── backend/ # z. B. NestJS-API mit Dockerfile
├── <app>/ # z. B. Flutter-App, auch mehrere
├── <homepage>/ # z. B. Hugo-Site
├── gitops-configuration/ # Deployment-Konfiguration für Dev und Prod
├── local-configuration/ # .vscode, .idea
└── gitlab-profile/ # README der Gruppe
Im geklonten Projektordner zeigen Symlinks auf local-configuration, deshalb sehen Editoren für alle Repos dieselben Einstellungen. Regeln für KI-Agenten kommen aus dem Agent-Plugin (mp agent install).
Vom Push zum laufenden Container
flowchart LR push["Push auf main"] -->|"Pipeline"| image["Docker-Image<br/>mit Versions-Tag"] image -->|"Tag eintragen"| gitops["gitops-configuration<br/>versions.yaml"] gitops -->|"Ansible"| dev["Dev-Server"] gitops -->|"Ansible, manueller Job"| prod["Prod-Server"]
Die Pipeline berechnet aus den Commit-Nachrichten die nächste Version (feat: und fix: zählen hoch), baut das Docker-Image, lädt es in die Registry und trägt den Tag in die Deployment-Konfiguration für Dev ein; das Deployment-Repository rollt ihn per Ansible auf den Dev-Server aus. Produktion bekommt denselben Tag erst über den manuellen Produktions-Job, den Git-Workflow und Deployment beschreibt.
Das GitOps-Repository
gitops-configuration/
└── configurations/
├── dev/
│ ├── generated.yaml # Plattform: Hostnamen, Registry, Secrets (SOPS)
│ ├── custom.yaml # du: eigene Umgebungsvariablen
│ └── versions.yaml # Pipeline: deployte Image-Versionen
└── prod/ # gleiche Struktur
generated.yaml schreibt MainPath bei jeder Projektänderung mit Hostnamen, Registry-Zugang und SOPS-verschlüsselten Secrets wie Datenbank-Passwörtern und JWT-Secrets. custom.yaml gehört dir und nimmt eigene Umgebungsvariablen unter additional_env_variables auf. versions.yaml schreibt die Pipeline mit dem deployten Image-Tag. Jeder Commit auf main, ob von der Pipeline oder von dir, löst das Ansible-Deployment der betroffenen Umgebung aus. Das Zusammenspiel mit den env/-Dateien steht unter Umgebungsvariablen.
Was auf dem Server läuft
Beim ersten Deployment richtet MainPath den Server per Ansible ein: Docker, Traefik als Reverse Proxy, eine Firewall, HTTPS-Zertifikate und einen Deploy-Schlüssel in den authorized_keys des angegebenen Benutzers. Traefik verteilt Anfragen anhand der Domain an den richtigen Container, auch an Docker Apps. Let’s Encrypt prüft die Domain standardmäßig per TLS-ALPN am Server; bei Cloudflare setzt MainPath die DNS-Einträge deshalb ohne Proxy. Ist der Server von außen nicht erreichbar, kannst du in den Details einer DNS-Anbindung optional DNS-01 anhaken.
Die Daten deines Backends liegen außerhalb der Container auf dem Server: ./backend-mysql für die Datenbank, ./backend-uploads für Uploads, die im Container unter /app/upload erscheinen, und ./backend-backups für die Archive von Easy Backup. Ein Release tauscht nur die Container aus.
Reine Homepage-Projekte auf einem Webspace lädt die Pipeline per FTP oder SFTP hoch, ohne Docker, GitOps und Ansible. Projekte auf einem Kubernetes-Cluster rollt GitOps per Helm aus; Datenbanken liegen dann als Cloud-Datenbank im Konto unter Cloud-Accounts. Auf klassischen Servern laufen Anwendungen weiterhin per Docker.
Generierte Konfiguration und Sentry
MainPath erzeugt .gitlab-ci.yml, env/*.generated.env und generated.yaml und überschreibt sie bei jeder Projektänderung; eigene Werte gehören in env/*.custom.env und custom.yaml. Aktivierst du Sentry für eine Komponente unter Funktionen, legt MainPath das Sentry-Projekt in deinem unter Anbindungen verbundenen Konto an und schreibt die DSN in diese Konfiguration, für Apps zum Beispiel in env/shared.generated.env. Den Umgang damit beschreibt Betrieb.