services: # MySQL Database for Semaphore mysql: image: mysql:8.0 container_name: semaphore-mysql restart: unless-stopped networks: - semaphore-internal environment: - TZ=Europe/Berlin - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-semaphore_root} - MYSQL_DATABASE=${MYSQL_DATABASE:-semaphore} - MYSQL_USER=${MYSQL_USER:-semaphore} - MYSQL_PASSWORD=${MYSQL_PASSWORD:-semaphore} volumes: - semaphore-mysql-data:/var/lib/mysql - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-semaphore_root}"] interval: 30s timeout: 10s retries: 3 start_period: 40s command: > --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci # Semaphore CI/CD Platform semaphore: image: semaphoreui/semaphore:latest container_name: semaphore restart: unless-stopped depends_on: mysql: condition: service_healthy networks: - semaphore-internal ports: # ONLY bind to localhost (127.0.0.1) - NOT accessible externally! # Default port 9300 to avoid conflict with Gitea (port 3000) # SECURITY: This ensures Semaphore is only accessible locally - "127.0.0.1:${SEMAPHORE_PORT:-9300}:3000" # NO Traefik labels - Semaphore should only be accessible locally! # External access is disabled for security reasons. environment: - TZ=Europe/Berlin # Database Configuration - SEMAPHORE_DB_DIALECT=mysql - SEMAPHORE_DB_HOST=mysql - SEMAPHORE_DB_PORT=3306 - SEMAPHORE_DB=${MYSQL_DATABASE:-semaphore} - SEMAPHORE_DB_USER=${MYSQL_USER:-semaphore} - SEMAPHORE_DB_PASS=${MYSQL_PASSWORD:-semaphore} # Admin Configuration - SEMAPHORE_ADMIN=${SEMAPHORE_ADMIN:-admin} - SEMAPHORE_ADMIN_NAME=${SEMAPHORE_ADMIN_NAME:-Administrator} - SEMAPHORE_ADMIN_EMAIL=${SEMAPHORE_ADMIN_EMAIL:-admin@localhost} - SEMAPHORE_ADMIN_PASSWORD=${SEMAPHORE_ADMIN_PASSWORD:-admin} # Playbook Path - SEMAPHORE_PLAYBOOK_PATH=${SEMAPHORE_PLAYBOOK_PATH:-/tmp/semaphore} # Encryption Key (generate with: head -c32 /dev/urandom | base64) - SEMAPHORE_ACCESS_KEY_ENCRYPTION=${SEMAPHORE_ACCESS_KEY_ENCRYPTION:-change-me-in-production} # Optional: LDAP Configuration (disabled by default) - SEMAPHORE_LDAP_ENABLED=${SEMAPHORE_LDAP_ENABLED:-false} # Optional: Webhook Configuration - SEMAPHORE_WEBHOOK_URL=${SEMAPHORE_WEBHOOK_URL:-} volumes: - semaphore-data:/etc/semaphore # Mount playbooks from repository so Semaphore can access them - ../../../deployment/stacks/semaphore/playbooks:/tmp/semaphore/playbooks:ro - ../../../deployment/ansible/playbooks:/tmp/semaphore/repo-playbooks:ro - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/"] interval: 30s timeout: 10s retries: 3 start_period: 40s volumes: semaphore-mysql-data: name: semaphore-mysql-data semaphore-data: name: semaphore-data networks: semaphore-internal: name: semaphore-internal driver: bridge