Files

102 lines
5.1 KiB
YAML

# =============================================================================
# feature-container-runtime-contract — dev environment override
#
# Merge with base:
# docker compose -f docker-compose.yml -f docker-compose.dev.yml up
#
# Dev intent:
# - Relaxes read-only filesystem (writable, no tmpfs constraint) so developers
# can hot-swap files without image rebuilds.
# - Increases memory allowance for dev/debugging workloads.
# - Disables restart-unless-stopped so crash loops don't mask startup errors.
# - Enables JMX remote port (local only, never in prod).
# - Mounts a local source volume for fast iteration (optional; mount when IDE
# attaches to the running container).
# =============================================================================
services:
app:
# Relax read-only constraint for local development.
read_only: false
# `!override`, not a plain empty list. An empty sequence merges with the base sequence rather
# than replacing it, so the base's /var/tmp/heap tmpfs survived and collided with the bind mount
# below — Compose refuses to have the same target twice and will not silently pick one. That is
# the right refusal: a heap dump written into a tmpfs dies with the container that produced it,
# which is the one moment somebody wants the file.
#
# `!override` needs Compose >= 2.24.4. Whether the collision is actually gone is checked in the
# merged model rather than assumed from this line.
tmpfs: !override []
# More memory for dev profiling / heap dumps.
mem_limit: 1g
memswap_limit: 1g
environment:
# Explicit, not inherited. A Compose profile selects services; it says nothing about which
# environment the application believes it is in, and the two drifting is how a dev stack ends
# up running local's settings.
SPRING_PROFILES_ACTIVE: "dev"
# The datasource address, owned here like the local and prod-smoke overlays own theirs. It was
# the only one of the three missing, and the gap was invisible while the qualification wrapper
# supplied a URL to every lane: the dev stack ran on a value that came from the test harness
# rather than from the file that describes the dev environment. With the wrapper no longer
# setting it — it was overriding prod's sslmode=verify-full URL — dev had none at all and
# Flyway was handed the literal string "${APP_DATASOURCE_URL}".
APP_DATASOURCE_URL: "jdbc:postgresql://db:5432/${POSTGRES_DB:-ca_skeleton}"
TZ: "UTC"
LANG: "C.UTF-8"
LC_ALL: "C.UTF-8"
# Use a shorter drain timeout in dev so restarts are faster.
APP_SERVER_SHUTDOWN_TIMEOUT: "5s"
APP_SERVER_SHUTDOWN: "graceful"
# Remote JMX — bind to localhost only; NEVER expose in production.
JAVA_TOOL_OPTIONS: >-
-XX:MaxRAMPercentage=75
-XX:+UseContainerSupport
-XX:+ExitOnOutOfMemoryError
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/tmp/heap
-Dserver.tomcat.basedir=/tmp
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.rmi.port=9999
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=127.0.0.1
ports:
- "8080:8080"
- "9001:9001"
- "127.0.0.1:9999:9999" # JMX — localhost only
# No health check constraint in dev; let the app start at its own pace.
healthcheck:
disable: true
# Do not restart automatically so crash loops stay visible.
restart: "no"
# Optional: mount heap dump directory to host for dev analysis.
# The base declares /var/tmp/heap as a tmpfs, which is right for an ephemeral runtime and wrong
# for dev: a heap dump written into a tmpfs dies with the container that produced it, which is
# the one moment somebody wants the file. Compose refuses to have both, and correctly — it will
# not silently pick one — so the tmpfs list is replaced rather than appended to.
#
# `!override` needs Compose >= 2.24.4. An empty sequence is not assumed to delete the base
# sequence by itself; scripts/verify-compose-profile-contracts.sh checks mount-target uniqueness
# in the merged model, which is what actually proves the collision is gone.
volumes:
- type: bind
source: ./tmp/heap-dumps
target: /var/tmp/heap
bind:
create_host_path: true
# The same network the shared infrastructure lives on. The local overlay joins it and the dev
# overlay did not, so a dev lane that started PostgreSQL beside the application put the two on
# different networks: `UnknownHostException: db`, from a container that was running and healthy
# a metre away. Compose puts a service with no `networks:` on `default`, which is a network of
# its own making — so the omission reads as a working stack until something has to resolve a
# name across it.
networks:
- caskeleton-infra
networks:
# Defined in docker-compose.infra.yml, where the services that share it live.
caskeleton-infra:
external: false