name: transfers services: postgresql: image: postgres:16.6 ports: - "8982:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] interval: 1s timeout: 2s retries: 300 env_file: - env.d/development/postgresql.defaults - env.d/development/postgresql.local redis: image: redis:5 ports: - "8985:6379" mailcatcher: image: maildev/maildev:2.2.1 ports: - "8984:1080" - "8988:1025" objectstorage: image: rustfs/rustfs:1.0.0-alpha.83 environment: - RUSTFS_ACCESS_KEY=transfers - RUSTFS_SECRET_KEY=password - RUSTFS_CONSOLE_ENABLE=true - RUSTFS_ADDRESS=0.0.0.0:9000 - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001 - RUSTFS_CORS_ALLOWED_ORIGINS=* - RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=* ports: - "8986:9000" - "8987:9001" healthcheck: test: [ "CMD", "sh", "-c", "curl -f http://127.0.0.1:9000/health && curl -f http://127.0.0.1:9001/rustfs/console/health", ] interval: 1s timeout: 5s retries: 60 start_period: 0s volumes: - objectstorage-data:/data # Local OIDC provider for dev — stands in for ProConnect so the real OIDC # login flow works without an external instance. --hostname pins every URL # Keycloak issues (issuer, authorize, token…) to localhost:8902 so they stay # consistent whether the browser (localhost:8902) or the backend # (keycloak:8802, in-network) talks to it. Realm + a test user are imported # from src/keycloak/realm.json. Admin console: http://localhost:8902 (admin/admin). keycloak: image: quay.io/keycloak/keycloak:26.6.1 command: - start-dev - --import-realm - --http-enabled=true - --http-port=8802 - --hostname=http://localhost:8902 - --hostname-admin=http://localhost:8902 - --proxy-headers=xforwarded environment: - KC_BOOTSTRAP_ADMIN_USERNAME=admin - KC_BOOTSTRAP_ADMIN_PASSWORD=admin # Expose /health on the management port (9000) so the healthcheck below # can confirm Keycloak is fully up (realm import included). - KC_HEALTH_ENABLED=true volumes: - ./src/keycloak/realm.json:/opt/keycloak/data/import/realm.json:ro ports: - "8902:8802" # The Keycloak image ships no curl/wget, so we probe /health/ready with # bash's /dev/tcp. Ready only flips true once startup + realm import finish, # which lets backend-dev gate on `service_healthy`. healthcheck: test: - CMD - bash - -c - "exec 3<>/dev/tcp/localhost/9000; printf 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3; grep -q '200 OK' <&3" interval: 5s timeout: 5s retries: 40 start_period: 15s backend-base: build: context: src/backend target: runtime-dev args: DOCKER_USER: ${DOCKER_USER:-1000} user: ${DOCKER_USER:-1000} volumes: - ./src/backend:/app - ./data/static:/data/static healthcheck: test: ["CMD", "python", "-c", "import urllib.request as u; u.urlopen('http://localhost:8000/__heartbeat__/', timeout=1)"] interval: 3s retries: 3 start_period: 10s backend-dev: extends: backend-base environment: - PYLINTHOME=/app/.pylint.d - DJANGO_CONFIGURATION=Development env_file: - env.d/development/backend.defaults - env.d/development/backend.local ports: - "8981:8000" depends_on: postgresql: condition: service_healthy objectstorage: condition: service_healthy redis: condition: service_started mailcatcher: condition: service_started keycloak: condition: service_healthy backend-db: extends: backend-base profiles: - tools environment: - DJANGO_CONFIGURATION=DevelopmentMinimal env_file: - env.d/development/backend.defaults - env.d/development/backend.local ports: - "8989:8000" depends_on: postgresql: condition: service_healthy backend-uv: profiles: - tools volumes: - ./src/backend:/app build: context: src/backend/ target: uv pull_policy: build worker-dev: build: context: src/backend target: runtime-dev args: DOCKER_USER: ${DOCKER_USER:-1000} user: ${DOCKER_USER:-1000} command: ["python", "worker.py", "--loglevel=DEBUG"] environment: - DJANGO_CONFIGURATION=Development env_file: - env.d/development/backend.defaults - env.d/development/backend.local volumes: - ./src/backend:/app - ./data/static:/data/static depends_on: - backend-dev worker-ui: build: context: src/backend target: runtime-dev args: DOCKER_USER: ${DOCKER_USER:-1000} user: ${DOCKER_USER:-1000} depends_on: - redis environment: - FLOWER_UNAUTHENTICATED_API=true - DJANGO_CONFIGURATION=Development env_file: - env.d/development/backend.defaults - env.d/development/backend.local volumes: - ./src/backend:/app ports: - "8983:8803" command: celery -A transfers.celery_app flower --port=8803 frontend-base: user: "${DOCKER_USER:-1000}" build: context: ./src/frontend dockerfile: Dockerfile target: frontend-deps args: DOCKER_USER: ${DOCKER_USER:-1000} frontend-dev: extends: frontend-base env_file: - env.d/development/frontend.defaults - env.d/development/frontend.local command: ["npm", "run", "dev"] volumes: - ./src/frontend/:/home/frontend/ # Anonymous volume — keeps the image-built node_modules from being # shadowed by whatever the host has (or doesn't have) at that path. # Critical on hosts where a stray ``npm install`` on the sshfs-mounted # project makes symlinks in ``node_modules/.bin/`` unreadable to # ``sh``, killing ``npm run dev`` with ``sh: vite: Operation not # permitted``. - /home/frontend/node_modules/ ports: - "8980:3000" frontend-tools: extends: frontend-base profiles: - frontend-tools volumes: - ./src/frontend/:/home/frontend/ - /home/frontend/node_modules/ volumes: objectstorage-data: