Replaces the bare-metal `composer run dev` workflow with a fully containerized 9-service stack orchestrated by docker compose. Single command brings up the full app — three subdomains (marketing / account / admin) reachable via Traefik with TLS, MariaDB + Valkey + Mailpit + Vite HMR + Horizon + scheduler all wired in. Components: - docker-compose.yml: traefik, app (php-fpm), web (nginx), mariadb, valkey, mailpit, vite, horizon, scheduler. - docker/: Dockerfiles, nginx config, entrypoint scripts. - Makefile: convenience targets (up / down / logs / shell / migrate / seed / test / pint / etc). - .env.docker.example: template for Docker-stack environment vars (separate from website/.env so bare-metal devs aren't disrupted). - website/vite.config.ts: server.host / origin / hmr / cors hooks driven by VITE_HOST / VITE_ORIGIN / VITE_HMR_HOST so the same config serves both bare-metal and Docker. - website/bootstrap/app.php: redirectGuestsTo() now uses request()->getScheme() so http: dev hosts don't get force-https redirects. - composer.json: drops laravel/sail (replaced by this stack). - docs/superpowers/specs/2026-04-25-docker-compose-dev-environment-design.md: full design spec. Bare-metal `composer run dev` workflow stays usable for anyone who prefers it — Docker stack reads .env.docker, doesn't fight website/.env. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
221 lines
7.2 KiB
YAML
221 lines
7.2 KiB
YAML
# ==============================================================================
|
|
# EZSCALE — Docker Compose dev environment
|
|
# Spec: docs/superpowers/specs/2026-04-25-docker-compose-dev-environment-design.md
|
|
#
|
|
# Quick start:
|
|
# cp .env.docker.example .env.docker
|
|
# make up
|
|
#
|
|
# Hostnames (auto-resolve to 127.0.0.1):
|
|
# https://ezscale.docker.localhost — marketing
|
|
# https://account.ezscale.docker.localhost — customer dashboard
|
|
# https://admin.ezscale.docker.localhost — admin panel
|
|
# https://account.ezscale.docker.localhost/horizon — Horizon dashboard
|
|
# https://mail.ezscale.docker.localhost — Mailpit UI
|
|
# https://vite.ezscale.docker.localhost — Vite dev server
|
|
# http://localhost:8080 — Traefik dashboard (insecure)
|
|
# ==============================================================================
|
|
|
|
name: ezscale
|
|
|
|
x-app-base: &app-base
|
|
build:
|
|
context: ./docker/app
|
|
args:
|
|
UID: ${UID:-1000}
|
|
GID: ${GID:-1000}
|
|
image: ezscale/app:dev
|
|
env_file:
|
|
- .env.docker
|
|
volumes:
|
|
- ./website:/var/www/html
|
|
networks:
|
|
- ezscale
|
|
depends_on:
|
|
mariadb:
|
|
condition: service_healthy
|
|
valkey:
|
|
condition: service_healthy
|
|
mailpit:
|
|
condition: service_started
|
|
|
|
services:
|
|
# ---------------------------------------------------------------------------
|
|
# Edge: Traefik (TLS + routing)
|
|
# ---------------------------------------------------------------------------
|
|
traefik:
|
|
image: traefik:v3.6
|
|
restart: unless-stopped
|
|
command: []
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
- "8080:8080"
|
|
volumes:
|
|
- ./docker/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
|
|
- ./docker/traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro
|
|
- traefik_certs:/etc/traefik/acme
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
networks:
|
|
- ezscale
|
|
labels:
|
|
- "traefik.enable=true"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PHP-FPM application container (php artisan serve replacement)
|
|
# ---------------------------------------------------------------------------
|
|
app:
|
|
<<: *app-base
|
|
container_name: ezscale-app
|
|
restart: unless-stopped
|
|
command: ["php-fpm"]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# nginx — serves public/, proxies PHP to app:9000
|
|
# ---------------------------------------------------------------------------
|
|
web:
|
|
build:
|
|
context: ./docker/nginx
|
|
image: ezscale/nginx:dev
|
|
restart: unless-stopped
|
|
volumes:
|
|
- ./website:/var/www/html:ro
|
|
- ./docker/nginx/conf.d:/etc/nginx/conf.d:ro
|
|
networks:
|
|
- ezscale
|
|
depends_on:
|
|
- app
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=ezscale"
|
|
- "traefik.http.services.ezscale-web.loadbalancer.server.port=80"
|
|
- "traefik.http.routers.ezscale-web.rule=Host(`ezscale.docker.localhost`) || Host(`account.ezscale.docker.localhost`) || Host(`admin.ezscale.docker.localhost`)"
|
|
- "traefik.http.routers.ezscale-web.entrypoints=web"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Vite dev server (HMR)
|
|
# ---------------------------------------------------------------------------
|
|
vite:
|
|
build:
|
|
context: ./docker/vite
|
|
args:
|
|
UID: ${UID:-1000}
|
|
GID: ${GID:-1000}
|
|
image: ezscale/vite:dev
|
|
restart: unless-stopped
|
|
working_dir: /var/www/html
|
|
environment:
|
|
VITE_HOST: "0.0.0.0"
|
|
VITE_ORIGIN: "http://vite.ezscale.docker.localhost"
|
|
VITE_HMR_HOST: "vite.ezscale.docker.localhost"
|
|
volumes:
|
|
- ./website:/var/www/html
|
|
networks:
|
|
- ezscale
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=ezscale"
|
|
- "traefik.http.services.ezscale-vite.loadbalancer.server.port=5173"
|
|
- "traefik.http.routers.ezscale-vite.rule=Host(`vite.ezscale.docker.localhost`)"
|
|
- "traefik.http.routers.ezscale-vite.entrypoints=web"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Horizon — queue worker supervisor (Redis-only)
|
|
# ---------------------------------------------------------------------------
|
|
horizon:
|
|
<<: *app-base
|
|
container_name: ezscale-horizon
|
|
restart: unless-stopped
|
|
command: ["php", "artisan", "horizon"]
|
|
stop_signal: SIGTERM
|
|
stop_grace_period: 60s
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Scheduler — runs `schedule:work` in a loop (cron replacement)
|
|
# ---------------------------------------------------------------------------
|
|
scheduler:
|
|
<<: *app-base
|
|
container_name: ezscale-scheduler
|
|
restart: unless-stopped
|
|
command: ["php", "artisan", "schedule:work"]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# MariaDB — primary database
|
|
# ---------------------------------------------------------------------------
|
|
mariadb:
|
|
image: mariadb:12
|
|
restart: unless-stopped
|
|
command:
|
|
- --character-set-server=utf8mb4
|
|
- --collation-server=utf8mb4_unicode_ci
|
|
- --skip-character-set-client-handshake
|
|
environment:
|
|
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root}
|
|
MARIADB_DATABASE: ${DB_DATABASE:-ezscale_billing}
|
|
MARIADB_USER: ${DB_USERNAME:-ezscale}
|
|
MARIADB_PASSWORD: ${DB_PASSWORD:-ezscale_local}
|
|
volumes:
|
|
- mariadb_data:/var/lib/mysql
|
|
- ./docker/mariadb/init:/docker-entrypoint-initdb.d:ro
|
|
networks:
|
|
- ezscale
|
|
healthcheck:
|
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 20
|
|
start_period: 30s
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Valkey — sessions, cache, queues
|
|
# ---------------------------------------------------------------------------
|
|
valkey:
|
|
image: valkey/valkey:9-alpine
|
|
restart: unless-stopped
|
|
command: ["valkey-server", "--appendonly", "yes"]
|
|
volumes:
|
|
- valkey_data:/data
|
|
networks:
|
|
- ezscale
|
|
healthcheck:
|
|
test: ["CMD", "valkey-cli", "ping"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 10
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Mailpit — SMTP catcher + web UI
|
|
# ---------------------------------------------------------------------------
|
|
mailpit:
|
|
image: axllent/mailpit:v1.29
|
|
restart: unless-stopped
|
|
environment:
|
|
MP_SMTP_AUTH_ACCEPT_ANY: "1"
|
|
MP_SMTP_AUTH_ALLOW_INSECURE: "1"
|
|
MP_MAX_MESSAGES: "5000"
|
|
volumes:
|
|
- mailpit_data:/data
|
|
networks:
|
|
- ezscale
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=ezscale"
|
|
- "traefik.http.services.ezscale-mail.loadbalancer.server.port=8025"
|
|
- "traefik.http.routers.ezscale-mail.rule=Host(`mail.ezscale.docker.localhost`)"
|
|
- "traefik.http.routers.ezscale-mail.entrypoints=web"
|
|
|
|
# ==============================================================================
|
|
# Volumes & networks
|
|
# ==============================================================================
|
|
|
|
volumes:
|
|
mariadb_data:
|
|
valkey_data:
|
|
mailpit_data:
|
|
traefik_certs:
|
|
|
|
networks:
|
|
ezscale:
|
|
name: ezscale
|
|
driver: bridge
|