Problem
Running docker compose --profile ui-dev up also starts the default ui container, since it has no profiles: key. Both bind port 4000, causing a conflict. Developers must manually docker compose stop ui before starting ui-dev.
Options
A. Add a profile to the ui service
Put ui under profiles: [default, ui] and ui-dev stays under profiles: [ui-dev]. Then:
docker compose --profile default up (or a wrapper script) starts the production-like UI
docker compose --profile ui-dev up starts the hot-reload UI
- Plain
docker compose up starts neither — this is the breaking change
Pro: Clean separation, no port conflict possible.
Con: Changes the default developer experience — everyone must pass --profile default (or --profile ui) to get a UI.
B. Use different ports
Change ui-dev to bind a different host port (e.g., 4001:4000). Both can run simultaneously.
Pro: No workflow change, backwards compatible.
Con: Developers need to know which port to use. Having both running wastes resources.
C. Use COMPOSE_PROFILES env var
Document setting COMPOSE_PROFILES=ui-dev in .env or shell profile to opt into dev mode. Keep ui as-is (no profile). Add profiles: [ui] to ui so it only starts when explicitly requested or when no profile is set... but Compose doesn't work that way.
This doesn't actually solve the conflict without also applying option A.
D. Override file approach
Move ui-dev into docker-compose.override.yml (git-ignored) that replaces the ui service with the dev variant. Developers copy a template file.
Pro: No profile juggling, docker compose up just works with the right UI.
Con: Another override file to manage. We already use this pattern for debugpy.
Recommendation
Option A is the cleanest long-term, but needs a CLAUDE.md / README update. Option D fits the existing override pattern we use for debugpy.
Problem
Running
docker compose --profile ui-dev upalso starts the defaultuicontainer, since it has noprofiles:key. Both bind port 4000, causing a conflict. Developers must manuallydocker compose stop uibefore startingui-dev.Options
A. Add a profile to the
uiservicePut
uiunderprofiles: [default, ui]andui-devstays underprofiles: [ui-dev]. Then:docker compose --profile default up(or a wrapper script) starts the production-like UIdocker compose --profile ui-dev upstarts the hot-reload UIdocker compose upstarts neither — this is the breaking changePro: Clean separation, no port conflict possible.
Con: Changes the default developer experience — everyone must pass
--profile default(or--profile ui) to get a UI.B. Use different ports
Change
ui-devto bind a different host port (e.g.,4001:4000). Both can run simultaneously.Pro: No workflow change, backwards compatible.
Con: Developers need to know which port to use. Having both running wastes resources.
C. Use
COMPOSE_PROFILESenv varDocument setting
COMPOSE_PROFILES=ui-devin.envor shell profile to opt into dev mode. Keepuias-is (no profile). Addprofiles: [ui]touiso it only starts when explicitly requested or when no profile is set... but Compose doesn't work that way.This doesn't actually solve the conflict without also applying option A.
D. Override file approach
Move
ui-devintodocker-compose.override.yml(git-ignored) that replaces theuiservice with the dev variant. Developers copy a template file.Pro: No profile juggling,
docker compose upjust works with the right UI.Con: Another override file to manage. We already use this pattern for debugpy.
Recommendation
Option A is the cleanest long-term, but needs a CLAUDE.md / README update. Option D fits the existing override pattern we use for debugpy.