mirror of
https://github.com/suitenumerique/messages.git
synced 2026-09-30 21:55:00 +02:00
Gradle and Xcode resolve the Capacitor plugins through relative paths into src/frontend/node_modules, so the named Docker volume that masked the host tree broke the IDE and the native builds. The dependencies are now installed in the bind mount (the bootstrap check follows), and the linked install layout is kept: `cap sync` bakes the real `.store/<pkg>@<ver>-<hash>` paths into the native project files, and those hashes derive from the lockfile, so the committed paths are valid on every machine sharing package-lock.json. The asset generation script requires packages it does not declare, which the linked layout no longer hoists for it: they are declared as devDependencies for that sole purpose.
607 lines
20 KiB
YAML
607 lines
20 KiB
YAML
name: st-messages
|
|
|
|
# Raise the open-file limit for the Django dev server + import workers. The
|
|
# Docker default (1024) is too low: the dev ``runserver`` keeps inbound HTTP
|
|
# keep-alive connections open with no idle timeout, so a long UI session polling
|
|
# the API (plus large imports opening S3 connections) exhausts FDs and wedges
|
|
# the backend with "Too many open files".
|
|
x-nofile-ulimits: &nofile-ulimits
|
|
nofile:
|
|
soft: 262144
|
|
hard: 262144
|
|
|
|
# Shared config for the Celery workers. worker-dev (full stack) and
|
|
# worker-dev-light (light stack) differ only in their profiles / depends_on /
|
|
# environment; everything else lives here. Note that ``environment`` is a list,
|
|
# so the ``<<`` merge does NOT combine it — each service repeats the full list.
|
|
x-worker-base: &worker-base
|
|
build:
|
|
context: src/backend
|
|
target: runtime-dev
|
|
args:
|
|
DOCKER_USER: ${DOCKER_USER:-1000}
|
|
user: ${DOCKER_USER:-1000}
|
|
ulimits: *nofile-ulimits
|
|
command: ["python", "worker.py", "--loglevel=DEBUG"]
|
|
env_file:
|
|
- deploy/env/backend.defaults
|
|
- deploy/env/backend.local
|
|
volumes:
|
|
- ./src/backend:/app
|
|
- ./data/static:/data/static
|
|
mem_limit: 2G
|
|
|
|
services:
|
|
postgresql:
|
|
image: postgres:16.6@sha256:557fea37a744d5f4c8faab304b0a90858b53ab119735a88c131fd19dab802f36
|
|
ports:
|
|
- "8912:5432"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
|
interval: 1s
|
|
timeout: 2s
|
|
retries: 300
|
|
env_file:
|
|
- deploy/env/postgresql.defaults
|
|
- deploy/env/postgresql.local
|
|
|
|
redis:
|
|
image: redis:5
|
|
ports:
|
|
- "8913:6379"
|
|
|
|
opensearch:
|
|
image: opensearchproject/opensearch:2.19.2@sha256:69588c664014fa3d3260ed5dd337f8538fec94790dad00bfbe92301eaaaac240
|
|
environment:
|
|
- discovery.type=single-node
|
|
- bootstrap.memory_lock=true
|
|
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
|
|
- "DISABLE_INSTALL_DEMO_CONFIG=true"
|
|
- "DISABLE_SECURITY_PLUGIN=true"
|
|
# - http.cors.enabled=true
|
|
# - "http.cors.allow-origin=/.*/"
|
|
ports:
|
|
- "8914:9200" # REST API
|
|
- "8915:9600" # Performance Analyzer
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:9200"]
|
|
interval: 1s
|
|
timeout: 5s
|
|
retries: 60
|
|
ulimits:
|
|
memlock:
|
|
soft: -1 # Set memlock to unlimited (no soft or hard limit)
|
|
hard: -1
|
|
nofile:
|
|
soft: 65536 # Maximum number of open files for the opensearch user - set to at least 65536
|
|
hard: 65536
|
|
|
|
mailcatcher:
|
|
image: maildev/maildev:2.2.1@sha256:180ef51f65eefebb0e7122d8308813c1fd7bff164bc440ce5a3c2feee167a810
|
|
ports:
|
|
- "8904:1080"
|
|
- "8917:1025"
|
|
|
|
objectstorage:
|
|
# user: ${DOCKER_USER:-1000}
|
|
image: rustfs/rustfs:1.0.0-alpha.83@sha256:1cfa82fb394a7c6a4ffc24f5333b52afee608d258503f94b37fcce5cc965c896
|
|
environment:
|
|
- RUSTFS_ACCESS_KEY=st-messages
|
|
- 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:
|
|
- "8906:9000"
|
|
- "8907: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
|
|
|
|
keycloak:
|
|
image: quay.io/keycloak/keycloak:26.7.4@sha256:82a77884f3af238beab1e7afd63b5f530e1b5c0590bd7aa60b40a40463e29b2c
|
|
volumes:
|
|
- ./src/keycloak/realm.json:/opt/keycloak/data/import/realm.json:ro
|
|
- ./src/keycloak/themes/dsfr-2.3.4.jar:/opt/keycloak/providers/keycloak-theme.jar:ro
|
|
- ./src/keycloak/bulk-role-membership/bulk-role-membership.jar:/opt/keycloak/providers/bulk-role-membership.jar:ro
|
|
environment:
|
|
- HOST=http://localhost:8902
|
|
- ADMIN_HOST=http://localhost:8902
|
|
command:
|
|
- start-dev
|
|
- --features=preview
|
|
- --import-realm
|
|
- --proxy-headers=xforwarded
|
|
- --http-enabled=true
|
|
- --hostname=$${HOST}
|
|
- --hostname-admin=$${ADMIN_HOST}
|
|
- --http-port=8802
|
|
# Expose the management health endpoints (:9000/health/*) so the
|
|
# healthcheck below can tell when Keycloak is actually serving.
|
|
- --health-enabled=true
|
|
env_file:
|
|
- deploy/env/keycloak.defaults
|
|
- deploy/env/keycloak.local
|
|
ports:
|
|
- "8902:8802"
|
|
# Without a healthcheck, `--wait` / `depends_on: service_started` consider
|
|
# Keycloak ready the instant the container process launches — but the JVM is
|
|
# still booting + importing the realm for several seconds, so :8902 500s.
|
|
# The image ships no curl/wget, so probe the management readiness endpoint
|
|
# (:9000/health/ready, 200 only once the realm import + DB are up) with
|
|
# bash's /dev/tcp.
|
|
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; head -n1 <&3 | grep -q ' 200'"
|
|
interval: 3s
|
|
timeout: 5s
|
|
retries: 40
|
|
# During start-up, probe every second (instead of `interval`) so Keycloak
|
|
# is detected healthy within ~1s of actually serving. start_period is set
|
|
# to comfortably cover a cold JVM boot + realm import so those early
|
|
# failures stay in the grace window.
|
|
start_period: 25s
|
|
start_interval: 1s
|
|
depends_on:
|
|
- postgresql
|
|
|
|
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
|
|
# Dev-only override: live-mount the jmap-email working tree over the
|
|
# package installed from PyPI so local source edits propagate without a
|
|
# rebuild. The wheel installed at
|
|
# ``/venv/lib/${PYTHON_VERSION}/site-packages/jmap_email`` is overlaid
|
|
# with the working-tree source. Override ``PYTHON_VERSION`` in the
|
|
# environment when the backend's Python floor moves. Comment this mount
|
|
# out to run exactly what CI/prod install from PyPI.
|
|
- ./src/jmap-email/jmap_email:/venv/lib/${PYTHON_VERSION:-python3.14}/site-packages/jmap_email
|
|
ulimits: *nofile-ulimits
|
|
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:
|
|
- deploy/env/backend.defaults
|
|
- deploy/env/backend.local
|
|
ports:
|
|
- "8901:8000"
|
|
depends_on:
|
|
postgresql:
|
|
condition: service_healthy
|
|
# restart: true
|
|
objectstorage:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_started
|
|
opensearch:
|
|
condition: service_healthy
|
|
keycloak:
|
|
condition: service_healthy
|
|
mailcatcher:
|
|
condition: service_started
|
|
|
|
# Lean backend for the default `make start`: same Development config (Redis
|
|
# cache + Celery broker, real worker) but WITHOUT OpenSearch, object storage
|
|
# or the MTAs — so a plain dev loop boots a handful of containers. Under the
|
|
# `light` profile so a bare `docker compose up` (make start-full) skips it;
|
|
# `make start` names it explicitly. `backend-dev` is left untouched because
|
|
# `make test-back` (docker compose run backend-dev) relies on its OpenSearch /
|
|
# object-storage deps to provision those for the search/storage test suites.
|
|
backend-dev-light:
|
|
extends: backend-base
|
|
profiles:
|
|
- light
|
|
environment:
|
|
- PYLINTHOME=/app/.pylint.d
|
|
- DJANGO_CONFIGURATION=Development
|
|
# No OpenSearch container in this stack: skip the per-delivery reindex
|
|
# enqueue so the worker isn't logging connection errors (search is a
|
|
# start-full feature).
|
|
- OPENSEARCH_INDEX_THREADS=False
|
|
env_file:
|
|
- deploy/env/backend.defaults
|
|
- deploy/env/backend.local
|
|
ports:
|
|
- "8901:8000"
|
|
depends_on:
|
|
postgresql:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_started
|
|
keycloak:
|
|
condition: service_healthy
|
|
|
|
backend-db:
|
|
extends: backend-base
|
|
profiles:
|
|
- tools
|
|
environment:
|
|
- DJANGO_CONFIGURATION=DevelopmentMinimal
|
|
env_file:
|
|
- deploy/env/backend.defaults
|
|
- deploy/env/backend.local
|
|
ports:
|
|
- "8901:8000"
|
|
depends_on:
|
|
postgresql:
|
|
condition: service_healthy
|
|
# restart: true
|
|
|
|
backend-uv:
|
|
profiles:
|
|
- tools
|
|
volumes:
|
|
- ./src/backend:/app
|
|
build:
|
|
context: src/backend
|
|
target: uv
|
|
pull_policy: build
|
|
|
|
worker-dev:
|
|
<<: *worker-base
|
|
environment:
|
|
- DJANGO_CONFIGURATION=Development
|
|
volumes:
|
|
- ./src/backend:/app
|
|
- ./data/static:/data/static
|
|
# Same dev-only jmap-email overlay as backend-base: the worker imports
|
|
# the same core code, so it must see the same library source.
|
|
- ./src/jmap-email/jmap_email:/venv/lib/${PYTHON_VERSION:-python3.14}/site-packages/jmap_email
|
|
depends_on:
|
|
- backend-dev
|
|
|
|
# Worker for the light stack (see backend-dev-light). Needs only the
|
|
# broker (Redis) + DB — not the web backend, not OpenSearch. Defined inline
|
|
# rather than via ``extends: worker-dev`` because ``extends`` drags in that
|
|
# service's ``depends_on: [backend-dev]``, which would pull the full backend
|
|
# (and its OpenSearch / object-storage deps) back into the light stack.
|
|
worker-dev-light:
|
|
<<: *worker-base
|
|
profiles:
|
|
- light
|
|
environment:
|
|
- DJANGO_CONFIGURATION=Development
|
|
- OPENSEARCH_INDEX_THREADS=False
|
|
depends_on:
|
|
postgresql:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_started
|
|
|
|
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:
|
|
- deploy/env/backend.defaults
|
|
- deploy/env/backend.local
|
|
volumes:
|
|
- ./src/backend:/app
|
|
# Same dev-only jmap-email overlay as backend-base: flower imports the
|
|
# same core code, so it must see the same library source.
|
|
- ./src/jmap-email/jmap_email:/venv/lib/${PYTHON_VERSION:-python3.14}/site-packages/jmap_email
|
|
ports:
|
|
- "8903:8803"
|
|
command: celery -A messages.celery_app flower --port=8803
|
|
|
|
frontend-base:
|
|
user: "${DOCKER_USER:-1000}"
|
|
build:
|
|
context: ./src/frontend
|
|
dockerfile: Dockerfile
|
|
# Lean image with no node_modules baked in — deps are installed into the
|
|
# bind-mounted source tree by `make install-frozen-front`.
|
|
target: frontend-dev-base
|
|
args:
|
|
DOCKER_USER: ${DOCKER_USER:-1000}
|
|
|
|
frontend-dev:
|
|
extends: frontend-base
|
|
env_file:
|
|
- deploy/env/frontend.defaults
|
|
- deploy/env/frontend.local
|
|
command: ["npm", "run", "dev"]
|
|
volumes:
|
|
# node_modules deliberately stays inside this bind mount. Moving it to a
|
|
# named volume makes installs faster, but the volume then masks the path
|
|
# on the host side, leaving src/frontend/node_modules empty — and the IDE
|
|
# (TypeScript, ESLint) and the native mobile builds (Gradle, Xcode/SPM,
|
|
# which resolve ../node_modules/@capacitor/*) read it from there. Mirroring
|
|
# the tree back out costs more than the install ever saved.
|
|
- ./src/frontend/:/home/frontend/
|
|
ports:
|
|
- "8900:3000"
|
|
|
|
frontend-tools:
|
|
extends: frontend-base
|
|
profiles:
|
|
- frontend-tools
|
|
volumes:
|
|
- ./src/backend/core/api/openapi.json:/home/backend/core/api/openapi.json
|
|
# `make install-frozen-front` runs here, so it installs straight into the
|
|
# host tree that frontend-dev and the native builds consume.
|
|
- ./src/frontend/:/home/frontend/
|
|
|
|
frontend-tools-amd64:
|
|
extends: frontend-tools
|
|
platform: linux/amd64
|
|
|
|
# Mobile (Capacitor) web build: like frontend-tools but with the env_file, so
|
|
# the NEXT_PUBLIC_* vars Vite inlines at build time are present (frontend-tools
|
|
# has none). The native projects (android/, ios/) are written back to the host
|
|
# through the mount; the native compile (gradle/xcode) stays a host step.
|
|
frontend-mobile:
|
|
extends: frontend-base
|
|
profiles:
|
|
- frontend-tools
|
|
# Run in the root group and register the (arbitrary) host uid in /etc/passwd
|
|
# before exec'ing the command, so the Capacitor CLI's os.userInfo() call does
|
|
# not throw ENOENT under musl. See the chmod in src/frontend/Dockerfile.
|
|
# Use DOCKER_UID (uid only) and not DOCKER_USER (uid:gid): appending ":0" to
|
|
# the latter yields a malformed "uid:gid:0" spec that drops the root group,
|
|
# leaving /etc/passwd read-only and breaking the registration above.
|
|
user: "${DOCKER_UID:-1000}:0"
|
|
entrypoint:
|
|
- /bin/sh
|
|
- -c
|
|
- 'grep -q ":x:$$(id -u):" /etc/passwd || echo "builder:x:$$(id -u):0::/home/frontend:/bin/sh" >> /etc/passwd; exec "$$@"'
|
|
- --
|
|
# The Capacitor CLI writes its config under $HOME/.config. HOME is unset for
|
|
# the arbitrary uid, so it falls back to "/" and fails with EACCES on
|
|
# mkdir /.config. Point it at a writable, non-mounted path (keeps the CLI's
|
|
# throwaway config out of the bind-mounted repo).
|
|
environment:
|
|
HOME: /tmp
|
|
env_file:
|
|
- deploy/env/frontend.defaults
|
|
- deploy/env/frontend.local
|
|
volumes:
|
|
# `cap sync` runs here and bakes the plugin paths it resolves into the
|
|
# committed native project files (Package.swift, capacitor.settings.gradle),
|
|
# which Xcode and Gradle then read from the host. Both sides must therefore
|
|
# resolve to the same node_modules — hence no masking volume here.
|
|
- ./src/frontend/:/home/frontend/
|
|
|
|
crowdin:
|
|
image: crowdin/cli:4.11.0@sha256:9b32eafcfd8ba4b5183bd601bfeb8a65b0d338f2d2fc36df155f4845ee244eff
|
|
volumes:
|
|
- ".:/app"
|
|
env_file:
|
|
- deploy/env/crowdin.defaults
|
|
- deploy/env/crowdin.local
|
|
user: "${DOCKER_USER:-1000}"
|
|
working_dir: /app
|
|
|
|
mta-in:
|
|
build:
|
|
context: src/mta-in
|
|
target: runtime-prod
|
|
env_file:
|
|
- deploy/env/mta-in.defaults
|
|
- deploy/env/mta-in.local
|
|
ports:
|
|
- "8910:25"
|
|
depends_on:
|
|
- backend-dev
|
|
|
|
mta-in-test:
|
|
profiles:
|
|
- tools
|
|
build:
|
|
context: src/mta-in
|
|
target: runtime-dev
|
|
env_file:
|
|
- deploy/env/mta-in.defaults
|
|
- deploy/env/mta-in.local
|
|
environment:
|
|
- EXEC_CMD=true
|
|
- MDA_API_BASE_URL=http://localhost:8000/api/mail/
|
|
- MTA_HOST=localhost
|
|
- MTA_PORT=25
|
|
- MTA_IMPL=postfix
|
|
command: pytest -vvs tests/
|
|
volumes:
|
|
- ./src/mta-in:/app
|
|
|
|
mta-in-uv:
|
|
profiles:
|
|
- tools
|
|
volumes:
|
|
- ./src/mta-in:/app
|
|
build:
|
|
context: src/mta-in
|
|
target: uv
|
|
pull_policy: build
|
|
|
|
# ---- Pure-Python (aiosmtpd) inbound MTA --------------------------------
|
|
# Runs side-by-side with the Postfix-based `mta-in` service on a different
|
|
# host port (8920 vs 8910). Both implementations share the same MDA
|
|
# contract, env vars, and test suite. Toggle which one is the public-facing
|
|
# MTA at the edge by switching the upstream pool.
|
|
|
|
mta-in-py:
|
|
build:
|
|
context: src/mta-in
|
|
dockerfile: Dockerfile.pymta
|
|
target: runtime-distroless-prod
|
|
args:
|
|
DOCKER_USER: ${DOCKER_USER:-65532}
|
|
user: ${DOCKER_USER:-65532}
|
|
env_file:
|
|
- deploy/env/mta-in.defaults
|
|
- deploy/env/mta-in.local
|
|
- deploy/env/mta-in-py.defaults
|
|
- deploy/env/mta-in-py.local
|
|
ports:
|
|
- "8920:25"
|
|
- "9120:9100" # Prometheus metrics
|
|
# Defence-in-depth: pymta needs no on-disk writes at runtime. Read-only
|
|
# rootfs + dropped capabilities + no-new-privileges mirror the posture
|
|
# a production k8s pod-spec should run with.
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
depends_on:
|
|
- backend-dev
|
|
|
|
mta-in-py-test:
|
|
profiles:
|
|
- tools
|
|
build:
|
|
context: src/mta-in
|
|
dockerfile: Dockerfile.pymta
|
|
target: runtime-dev
|
|
args:
|
|
DOCKER_USER: ${DOCKER_USER:-65532}
|
|
user: ${DOCKER_USER:-65532}
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
env_file:
|
|
- deploy/env/mta-in.defaults
|
|
- deploy/env/mta-in.local
|
|
- deploy/env/mta-in-py.defaults
|
|
- deploy/env/mta-in-py.local
|
|
environment:
|
|
- EXEC_CMD=true
|
|
- MDA_API_BASE_URL=http://localhost:8000/api/mail/
|
|
- MTA_HOST=localhost
|
|
- MTA_PORT=25
|
|
- MTA_IMPL=pymta
|
|
- MTA_METRICS_URL=http://localhost:9100/metrics
|
|
# Self-signed cert baked into the runtime-dev stage, so STARTTLS is
|
|
# advertised and tests/test_smtp_protocol.py can exercise the TLS path.
|
|
- PYMTA_TLS_CERT_FILE=/certs/pymta-test.crt
|
|
- PYMTA_TLS_KEY_FILE=/certs/pymta-test.key
|
|
command: pytest -vvs tests/
|
|
volumes:
|
|
- ./src/mta-in:/app
|
|
|
|
socks-proxy:
|
|
build:
|
|
context: src/socks-proxy
|
|
target: runtime
|
|
env_file:
|
|
- deploy/env/socks-proxy.defaults
|
|
- deploy/env/socks-proxy.local
|
|
ports:
|
|
- "8916:1080"
|
|
|
|
socks-proxy-test:
|
|
profiles:
|
|
- tools
|
|
build:
|
|
context: src/socks-proxy/tests
|
|
environment:
|
|
- SOCKS_PROXY1=user1:pwd1@socks-proxy:1080
|
|
- SOCKS_PROXY2=user2:pwd2@socks-proxy:1080
|
|
depends_on:
|
|
socks-proxy:
|
|
condition: service_started
|
|
|
|
mpa:
|
|
build:
|
|
context: src/mpa/rspamd
|
|
environment:
|
|
- RSPAMD_password=password
|
|
- PORT=8010
|
|
ports:
|
|
- "8918:8010"
|
|
depends_on:
|
|
redis:
|
|
condition: service_started
|
|
|
|
mpa-test:
|
|
profiles:
|
|
- tools
|
|
build:
|
|
context: src/mpa/tests
|
|
environment:
|
|
- RSPAMD_URL=http://mpa:8010/_api
|
|
- RSPAMD_AUTH=Bearer password
|
|
command: pytest -vvs tests/
|
|
volumes:
|
|
- ./src/mpa/tests:/app/tests
|
|
depends_on:
|
|
mpa:
|
|
condition: service_started
|
|
redis:
|
|
condition: service_started
|
|
|
|
# Self-contained jmap-email package tests. Zero infrastructure
|
|
# dependencies (no DB, no opensearch, no redis) — the library has
|
|
# no runtime deps. Source is mounted for instant feedback during
|
|
# development.
|
|
jmap-email-test:
|
|
profiles:
|
|
- tools
|
|
build:
|
|
context: src/jmap-email
|
|
command: pytest -q tests/
|
|
environment:
|
|
# Hypothesis examples per `-m fuzz` test. The default matches
|
|
# FUZZ_SETTINGS in the suite; CI lowers it on pull requests so the
|
|
# job stays a few minutes and runs the full sweep on main.
|
|
FUZZ_EXAMPLES: ${FUZZ_EXAMPLES:-10000}
|
|
volumes:
|
|
- ./src/jmap-email/jmap_email:/app/jmap_email
|
|
- ./src/jmap-email/tests:/app/tests
|
|
- ./src/jmap-email/pyproject.toml:/app/pyproject.toml
|
|
# Hypothesis' example database. Without it the container starts with
|
|
# no memory of past failures, so Phase.reuse has nothing to replay
|
|
# and an intermittent find stays intermittent.
|
|
#
|
|
# A named volume rather than a bind mount: a bind mount to a path
|
|
# that does not exist yet — and it never does on a fresh clone,
|
|
# since .hypothesis is gitignored — is created by Docker as *root*,
|
|
# which then blocks `git clean -fdx` and any pytest run against a
|
|
# local venv. This is a cache, so nothing needs to read it from the
|
|
# host.
|
|
- jmap-email-hypothesis:/app/.hypothesis
|
|
|
|
volumes:
|
|
jmap-email-hypothesis:
|
|
objectstorage-data:
|