166 lines
5.6 KiB
YAML
166 lines
5.6 KiB
YAML
volumes:
|
|
postgres_data:
|
|
jenkins_home:
|
|
gitea_data:
|
|
traefik_letsencrypt:
|
|
|
|
networks:
|
|
proxy: # internet-facing network, containers reachable via Traefik
|
|
driver: bridge
|
|
name: proxy
|
|
internal: # no internet access; for DB + backend services only
|
|
driver: bridge
|
|
internal: true
|
|
name: internal
|
|
socket-proxy: # isolated network just for Traefik <-> Docker API access
|
|
driver: bridge
|
|
name: socket-proxy
|
|
|
|
services:
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Restricts Docker API access so Traefik never touches the raw socket.
|
|
# Only exposes the read-only "list containers" endpoint it needs to
|
|
# discover services and their labels.
|
|
# ---------------------------------------------------------------------
|
|
socket-proxy:
|
|
image: tecnativa/docker-socket-proxy:0.3
|
|
container_name: socket-proxy
|
|
restart: unless-stopped
|
|
environment:
|
|
CONTAINERS: 1
|
|
SERVICES: 0
|
|
TASKS: 0
|
|
NETWORKS: 0
|
|
POST: 0
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
networks:
|
|
- socket-proxy
|
|
|
|
traefik:
|
|
image: traefik:v3.6
|
|
container_name: traefik
|
|
restart: unless-stopped
|
|
command:
|
|
- "--api.dashboard=true"
|
|
- "--api.insecure=false"
|
|
- "--providers.docker=true"
|
|
- "--providers.docker.endpoint=tcp://socket-proxy:2375"
|
|
- "--providers.docker.exposedbydefault=false"
|
|
- "--entrypoints.web.address=:80"
|
|
- "--entrypoints.websecure.address=:443"
|
|
- "--certificatesresolvers.le.acme.email=${ACME_EMAIL}"
|
|
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
|
|
- "--certificatesresolvers.le.acme.httpchallenge=true"
|
|
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
|
|
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
|
|
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
|
|
- "--log.level=WARN"
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- traefik_letsencrypt:/letsencrypt
|
|
networks:
|
|
- proxy
|
|
- socket-proxy
|
|
labels:
|
|
- "traefik.enable=true"
|
|
# Global security headers applied to every router below
|
|
- "traefik.http.middlewares.sec-headers.headers.stsSeconds=31536000"
|
|
- "traefik.http.middlewares.sec-headers.headers.stsIncludeSubdomains=true"
|
|
- "traefik.http.middlewares.sec-headers.headers.forceSTSHeader=true"
|
|
- "traefik.http.middlewares.sec-headers.headers.frameDeny=true"
|
|
- "traefik.http.middlewares.sec-headers.headers.contentTypeNosniff=true"
|
|
- "traefik.http.middlewares.sec-headers.headers.browserXssFilter=true"
|
|
|
|
postgres:
|
|
image: postgres:16
|
|
container_name: postgres
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: ${POSTGRES_DB}
|
|
POSTGRES_USER: ${POSTGRES_USER}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
networks:
|
|
- internal # no "proxy" network, no published ports -> unreachable from outside
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 5
|
|
|
|
apache:
|
|
image: httpd:2.4
|
|
container_name: website
|
|
restart: unless-stopped
|
|
volumes:
|
|
- ./website:/usr/local/apache2/htdocs:ro
|
|
networks:
|
|
- proxy
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.website.rule=Host(`${DOMAIN}`) || Host(`www.${DOMAIN}`)"
|
|
- "traefik.http.routers.website.entrypoints=websecure"
|
|
- "traefik.http.routers.website.tls.certresolver=le"
|
|
- "traefik.http.routers.website.middlewares=sec-headers@docker"
|
|
- "traefik.http.services.website.loadbalancer.server.port=80"
|
|
|
|
gitea:
|
|
image: gitea/gitea:1.22
|
|
container_name: gitea
|
|
restart: unless-stopped
|
|
environment:
|
|
USER_UID: 1000
|
|
USER_GID: 1000
|
|
GITEA__server__ROOT_URL: https://git.${DOMAIN}/
|
|
volumes:
|
|
- gitea_data:/data
|
|
networks:
|
|
- proxy
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.gitea.rule=Host(`git.${DOMAIN}`)"
|
|
- "traefik.http.routers.gitea.entrypoints=websecure"
|
|
- "traefik.http.routers.gitea.tls.certresolver=le"
|
|
- "traefik.http.routers.gitea.middlewares=sec-headers@docker"
|
|
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
|
|
|
|
jenkins:
|
|
image: jenkins/jenkins:lts-jdk21
|
|
container_name: jenkins
|
|
restart: unless-stopped
|
|
volumes:
|
|
- jenkins_home:/var/jenkins_home
|
|
- /var/run/docker.sock:/var/run/docker.sock # see README: known risk, needed for CI docker builds
|
|
group_add:
|
|
- "988" # host docker group GID
|
|
networks:
|
|
- proxy
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.jenkins.rule=Host(`ci.${DOMAIN}`)"
|
|
- "traefik.http.routers.jenkins.entrypoints=websecure"
|
|
- "traefik.http.routers.jenkins.tls.certresolver=le"
|
|
- "traefik.http.routers.jenkins.middlewares=sec-headers@docker"
|
|
- "traefik.http.services.jenkins.loadbalancer.server.port=8080"
|
|
|
|
api:
|
|
build: ./api
|
|
container_name: api
|
|
restart: unless-stopped
|
|
networks:
|
|
- proxy
|
|
- internal # future: talk to postgres directly, still no internet exposure beyond Traefik
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=proxy"
|
|
- "traefik.http.routers.api.rule=Host(`api.${DOMAIN}`)"
|
|
- "traefik.http.routers.api.entrypoints=websecure"
|
|
- "traefik.http.routers.api.tls.certresolver=le"
|
|
- "traefik.http.routers.api.middlewares=sec-headers@docker"
|
|
- "traefik.http.services.api.loadbalancer.server.port=3000"
|