# Gitea behind traefik, with rproxy and servewaf in front of it. # docs/gitea.md says what each setting here does and why. Before starting it: # - set SMALLWEBWAF_IMAGE to the smallwebwaf image, named by digest; # - replace git.example.org with the site's host name; # - replace each 10.99.0 with the first three numbers of a /24 subnet that # no other network on the host uses. # Then run `docker compose up --detach` in this directory. # script/gitea-setup checks this setup. name: gitea x-logging: &logging driver: json-file options: max-size: 10m max-file: "5" services: traefik: # traefik v3.7.17, 2026-11-03 image: traefik@sha256:68ae5fe5b64ca166704b746a9e8afcd385e36b3f569a5691401bf652d57b3748 # traefik replaces the X-Forwarded-For header a visitor sends. Keep it # so: otherwise a visitor could name an address on the shared network, # and skip servewaf's checks. command: - --providers.docker.exposedbydefault=false - --entrypoints.web.address=:80 - --entrypoints.web.http.redirections.entrypoint.to=websecure - --entrypoints.websecure.address=:443 # Until a certificate resolver is added here, as for your other # sites, traefik answers with a certificate it made itself. - --entrypoints.websecure.http.tls=true # traefik's own limit of 60 seconds for a whole request would cut # off an upload long before rproxy's limit of 30 minutes. - --entrypoints.websecure.transport.respondingTimeouts.readTimeout=30m ports: - 80:80 - 443:443 volumes: - /var/run/docker.sock:/var/run/docker.sock:ro networks: shared: # Containers on the shared network find traefik under the # site's name, and so reach it from their own address. aliases: - git.example.org restart: unless-stopped gitea: # gitea 1.27.3, 2026-08-29 image: gitea/gitea@sha256:87a67ee09d3ae0d1df5fda5dcda3e2a1f9236a45b0a59025d6e00e46adc43bef environment: GITEA__server__ROOT_URL: https://git.example.org/ # Gitea starts at its defaults, with SQLite, instead of showing its # install page to whoever opens it first. GITEA__security__INSTALL_LOCK: "true" # rproxy's address. Gitea then takes each visitor's address from # the X-Real-Ip header traefik sets and rproxy passes on. GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES: 10.99.0.2 volumes: - gitea-data:/data networks: - shared restart: unless-stopped rproxy: image: ${SMALLWEBWAF_IMAGE:?set SMALLWEBWAF_IMAGE to the smallwebwaf image} environment: SWRP_WAF_BACKEND: http://servewaf:8080 SWRP_UPSTREAM_URL: http://gitea:3000 # Pushes, LFS objects, packages and release attachments of up to # 2 GiB, sent in up to 30 minutes. SWRP_MAX_REQUEST_SIZE: 2G SWRP_MAX_REQUEST_TIME_MS: "1800000" # 15 minutes for a clone or a download, half the default. SWRP_MAX_RESPONSE_TIME_MS: "900000" labels: traefik.enable: "true" traefik.http.routers.gitea.rule: Host(`git.example.org`) traefik.http.routers.gitea.entrypoints: websecure traefik.http.services.gitea.loadbalancer.server.port: "8080" networks: shared: ipv4_address: 10.99.0.2 logging: *logging restart: unless-stopped servewaf: image: ${SMALLWEBWAF_IMAGE:?set SMALLWEBWAF_IMAGE to the smallwebwaf image} environment: SMALLWEBWAF_COMMAND: servewaf # The containers on the shared network skip the checks. Add the # address of an automation fleet on another host here too; without # that, the default request limits are sized for such a fleet. SWWAF_ALLOW_NETS: 10.99.0.128/25 # In block mode the Core Rule Set refuses everyday Gitea searches # and file names, and 31 refusals in a minute ban the address. SWWAF_WAF_MODE: detect volumes: - servewaf-state:/var/lib/smallwebwaf networks: - shared logging: *logging restart: unless-stopped networks: # The network traefik and Gitea share. Its containers get addresses in its # upper half, which servewaf lets skip the checks. The gateway, the host's # own address on it, stays out of that half: requests docker passes on from # a published port, such as those of visitors over IPv6, can come from it. shared: ipam: config: - subnet: 10.99.0.0/24 ip_range: 10.99.0.128/25 gateway: 10.99.0.1 volumes: gitea-data: servewaf-state: