# Architektur

GitLab-Pipelines bauen Docker-Images, ein GitOps-Repository hält fest, welche Version wo läuft, und Ansible bringt diesen Stand auf Server, die die Plattform mit Traefik, Firewall und HTTPS eingerichtet hat.

> Source: https://www.application-platform.com/de/docs/architecture/

Jedes Projekt ist eine Gruppe im GitLab der Plattform unter `gitlab.application-platform.com` mit einem Repository pro Komponente, `local-configuration` für die gemeinsame Agenten-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/      # AGENTS.md, CLAUDE.md, .cursor, .claude, .codex, .vscode
└── gitlab-profile/           # README der Gruppe
```

Im geklonten Projektordner zeigen Symlinks auf `local-configuration`, deshalb sehen KI-Agenten und Editoren für alle Repos dieselben Regeln.

## Vom Push zum laufenden Container

```mermaid
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]({{< relref "git-workflow" >}}) 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 die Plattform 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]({{< relref "environment-setup" >}}).

## Was auf dem Server läuft

Beim ersten Deployment richtet die Plattform 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]({{< relref "hosted-apps" >}}). Für Let's Encrypt muss der Server direkt erreichbar sein; bei Cloudflare setzt die Plattform die DNS-Einträge deshalb ohne Proxy.

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]({{< relref "webspace-ftp" >}}) lädt die Pipeline per FTP oder SFTP hoch, ohne Docker, GitOps und Ansible. Ein Kubernetes-Zugang unter **Anbindungen** wird bislang nur gespeichert; alle Anwendungen laufen per Docker auf dem Server.

## Generierte Konfiguration und Sentry

Die Plattform 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 die Plattform 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]({{< relref "operations" >}}).

