# Architecture

GitLab pipelines build Docker images, a GitOps repository records which version runs where, and Ansible brings that state to servers that MainPath has set up with Traefik, a firewall, and HTTPS.

> Source: https://www.mainpath.ai/en/docs/architecture/

Every project is a group in MainPath's GitLab at `gitlab.application-platform.com` with one repository per component, `local-configuration` for the shared editor configuration, `gitops-configuration` for the deployment, and `gitlab-profile` with the README.

```
orgs/<organization>/<project>/
├── backend/                  # e.g. NestJS API with Dockerfile
├── <app>/                    # e.g. Flutter app, several possible
├── <homepage>/               # e.g. Hugo site
├── gitops-configuration/     # deployment configuration for dev and prod
├── local-configuration/      # .vscode, .idea
└── gitlab-profile/           # group README
```

In the cloned project folder, symlinks point to `local-configuration`, so editors see the same settings for all repos. Rules for AI agents come from the agent plugin (`mp agent install`).

## From push to running container

```mermaid
flowchart LR
  push["Push to main"] -->|"pipeline"| image["Docker image<br/>with version tag"]
  image -->|"write tag"| gitops["gitops-configuration<br/>versions.yaml"]
  gitops -->|"Ansible"| dev["Dev server"]
  gitops -->|"Ansible, manual job"| prod["Prod server"]
```

The pipeline computes the next version from the commit messages (`feat:` and `fix:` increment it), builds the Docker image, pushes it to the registry, and writes the tag into the deployment configuration for dev; the deployment repository rolls it out to the dev server with Ansible. Production receives the same tag only through the manual production job, which [Git workflow and deployment]({{< relref "git-workflow" >}}) describes.

## The GitOps repository

```
gitops-configuration/
└── configurations/
    ├── dev/
    │   ├── generated.yaml   # platform: hostnames, registry, secrets (SOPS)
    │   ├── custom.yaml      # you: your own environment variables
    │   └── versions.yaml    # pipeline: deployed image versions
    └── prod/                # same structure
```

MainPath writes `generated.yaml` on every project change with hostnames, registry access, and SOPS-encrypted secrets such as database passwords and JWT secrets. `custom.yaml` belongs to you and takes your own environment variables under `additional_env_variables`. The pipeline writes `versions.yaml` with the deployed image tag. Every commit to `main`, whether from the pipeline or from you, triggers the Ansible deployment of the affected environment. How these files interact with the `env/` files is described in [Environment variables]({{< relref "environment-setup" >}}).

## What runs on the server

On the first deployment, MainPath sets up the server with Ansible: Docker, Traefik as the reverse proxy, a firewall, HTTPS certificates, and a deploy key in the `authorized_keys` of the specified user. Traefik routes requests by domain to the right container, including [Docker Apps]({{< relref "hosted-apps" >}}). Let's Encrypt verifies the domain with TLS-ALPN on the server by default; with Cloudflare, MainPath therefore sets the DNS records without the proxy. If the server is not reachable from the internet, you can optionally tick DNS-01 in a [DNS connection]({{< relref "connections" >}})'s details.

The data of your backend lives outside the containers on the server: `./backend-mysql` for the database, `./backend-uploads` for uploads, which appear inside the container at `/app/upload`, and `./backend-backups` for the archives of Easy Backup. A release replaces only the containers.

The pipeline uploads pure homepage projects on a [web space]({{< relref "webspace-ftp" >}}) via FTP or SFTP, without Docker, GitOps, and Ansible. Projects on a [Kubernetes cluster]({{< relref "kubernetes-integration" >}}) are rolled out with Helm by GitOps; databases then live as a cloud database in the account under [Cloud accounts]({{< relref "cloud-accounts" >}}). On classic servers, applications still run with Docker.

## Generated configuration and Sentry

MainPath generates `.gitlab-ci.yml`, `env/*.generated.env`, and `generated.yaml` and overwrites them on every project change, so your own values belong in `env/*.custom.env` and `custom.yaml`. If you enable Sentry for a component under **Features**, MainPath creates the Sentry project in the account connected under **Connections** and writes the DSN into this configuration, for apps into `env/shared.generated.env`, for example. [Operations]({{< relref "operations" >}}) describes how to work with them.

