services: caddy: image: caddy:2.11.4-alpine container_name: caddy volumes: - caddy_data:/data - caddy_config:/config - ./Caddyfile:/etc/caddy/Caddyfile restart: unless-stopped ports: - "8080:8080" - "8082:8082" - "8084:8084" logging: driver: local options: mode: non-blocking max-size: 10m max-file: "3" web: container_name: web image: ghcr.io/oxynote/web:dev environment: # SSR inside the container must reach the services directly; these # compose hostnames don't resolve on the host, which is why they live # here and not in the shared env template. - NUXT_CORE_API_INTERNAL_HTTP_URL=http://core:8080 - NUXT_AUTH_REALTIME_API_INTERNAL_HTTP_URL=http://auth-realtime:8081 # not started by plain `up` — `make dev` runs web on the host instead; # `make run` / `make start` enable this profile for the full stack. profiles: - web build: context: ../web dockerfile: Dockerfile.dev env_file: - ./env/web.local.env restart: unless-stopped logging: driver: local options: mode: non-blocking max-size: 10m max-file: "3" auth-realtime: container_name: auth-realtime image: ghcr.io/oxynote/auth-realtime:dev build: context: ../server/auth-realtime dockerfile: Dockerfile.dev env_file: - ./env/auth-realtime.local.env depends_on: postgres: condition: service_healthy valkey: condition: service_healthy restart: unless-stopped logging: driver: local options: mode: non-blocking max-size: 10m max-file: "3" core: container_name: core image: ghcr.io/oxynote/core:dev env_file: - ./env/core.local.env depends_on: postgres: condition: service_healthy volumes: # optional GitHub App private key: drop oxynote.private-key.pem into # docker/github/ and set OXYNOTE_CORE_GITHUB_APP_ID to enable the # GitHub App integration. - ./github:/etc/oxynote/core/github:ro # uploaded files, which core keeps under OXYNOTE_CORE_OBJECT_STORAGE_LOCAL_PATH # when no object store is configured, and the search index under # OXYNOTE_CORE_SEARCH_INDEX_PATH. The mount is the home directory # rather than the storage path itself: core runs as a non-root uid # that owns only /home/nonroot in its image, and an empty volume # takes the ownership of the directory it is mounted over. - core_storage:/home/nonroot restart: unless-stopped logging: driver: local options: mode: non-blocking max-size: 10m max-file: "3" datagen: container_name: datagen image: ghcr.io/oxynote/datagen:dev environment: - DATAGEN_POSTGRES_DSN=postgresql://demouser:demopass@postgres/demodb?sslmode=disable - DATAGEN_MARIADB_DSN=demouser:demopass@tcp(mariadb:3306)/demodb?parseTime=true depends_on: postgres: condition: service_healthy mariadb: condition: service_healthy restart: unless-stopped logging: driver: local options: mode: non-blocking max-size: 10m max-file: "3" postgres: image: postgres:18.6-alpine command: ["postgres", "-c", "log_checkpoints=off"] volumes: - postgres_data:/var/lib/postgresql - ./demo/postgres/init-demo-db.sh:/docker-entrypoint-initdb.d/init-demo-db.sh environment: POSTGRES_USER: devuser POSTGRES_PASSWORD: devpass POSTGRES_DB: devdb restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U devuser -d devdb"] interval: 10s timeout: 5s retries: 5 logging: driver: local options: mode: non-blocking max-size: 10m max-file: "3" mariadb: image: mariadb:12.3.3-noble command: ["--log-warnings=1"] volumes: - mariadb_data:/var/lib/mysql - ./demo/mariadb/init-demo-db.sql:/docker-entrypoint-initdb.d/init-demo-db.sql environment: MARIADB_ROOT_PASSWORD: devroot MARIADB_USER: devuser MARIADB_PASSWORD: devpass MARIADB_DATABASE: devdb restart: unless-stopped healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 10s timeout: 5s retries: 5 logging: driver: local options: mode: non-blocking max-size: 10m max-file: "3" mailpit: container_name: mailpit image: axllent/mailpit:v1.31.1 environment: - MP_QUIET=true restart: unless-stopped logging: driver: local options: mode: non-blocking max-size: 10m max-file: "3" valkey: container_name: valkey image: valkey/valkey:9.1.2-alpine command: ["valkey-server", "--loglevel", "warning"] volumes: - valkey_data:/data restart: unless-stopped healthcheck: test: ["CMD", "valkey-cli", "ping"] interval: 10s timeout: 5s retries: 5 logging: driver: local options: mode: non-blocking max-size: 10m max-file: "3" changedetection: container_name: changedetection image: dgtlmoon/changedetection.io:0.60.4 volumes: - changedetection_data:/datastore environment: - HIDE_REFERER=true - LOGGER_LEVEL=WARNING - PLAYWRIGHT_DRIVER_URL=ws://browser-sockpuppet-chrome:3000 depends_on: browser-sockpuppet-chrome: condition: service_started restart: unless-stopped logging: driver: local options: mode: non-blocking max-size: 10m max-file: "3" browser-sockpuppet-chrome: container_name: browser-sockpuppet-chrome hostname: browser-sockpuppet-chrome # upstream publishes no per-release tags (its version tags lag latest by # months), so latest is pinned by digest instead; dependabot bumps the # digest when a new latest is published. image: dgtlmoon/sockpuppetbrowser:latest@sha256:a61e64a694fef3b6d375a3c7c7dd7d74b1166a48b231cd98870b78f244deef79 cap_add: - SYS_ADMIN # TODO: Recommended in their docker-compose example, need to double-check this. restart: unless-stopped environment: - SCREEN_WIDTH=1920 - SCREEN_HEIGHT=1024 - SCREEN_DEPTH=16 - MAX_CONCURRENT_CHROME_PROCESSES=10 - LOG_LEVEL=WARNING grafana: image: grafana/grafana:13.2.1 container_name: grafana ports: - "8083:3000" volumes: - grafana_storage:/var/lib/grafana environment: - GF_SECURITY_ADMIN_USER=admin - GF_SECURITY_ADMIN_PASSWORD=admin - GF_LOG_LEVEL=error volumes: core_storage: mariadb_data: postgres_data: valkey_data: caddy_data: caddy_config: changedetection_data: grafana_storage: