How Dream Server Handles Port Conflicts When Merging Docker Compose Files
Dream Server prevents port conflicts by validating host-port assignments across layered compose fragments before runtime, using environment-driven parameter substitution and a dedicated resolution script that aborts on collision detection.
Light-Heart-Labs/DreamServer orchestrates complex container stacks by merging a base composition with GPU overlays and extension-provided fragments. To ensure that no two services claim the same host interface, the project implements a deterministic port contract validation system that intercepts conflicts during the build phase rather than at runtime.
The Multi-Layer Compose Architecture
Dream Server constructs its final Docker Compose stack through a hierarchical merge process. The foundation rests on docker-compose.base.yml, which is then overlaid with optional GPU configurations and any extension fragments found in extensions/services/*/compose.yaml.
Each service declares its network requirements using parameterized port mappings (e.g., ${WEBUI_PORT:-3000}) rather than hardcoded integers. These parameters pull values from the generated .env file, where defaults are centrally defined in .env.example. This architecture ensures that every service’s host binding remains configurable while maintaining a single source of truth for port allocation.
The Port Contract Validation Pipeline
Before executing any docker compose command, the helper script scripts/resolve-compose-stack.sh performs a deterministic validation sweep across all resolved compose files. This pipeline enforces three critical safety guarantees:
Canonical Port Definitions via Environment Variables
Every extension and base service defines its exposed ports using shell parameter expansion syntax. For example, the dashboard service might declare:
ports:
- "${DASHBOARD_PORT:-3000}:3000"
The default values for these variables reside in .env.example, allowing the resolution script to substitute values at compose-merge time rather than runtime. This approach ensures that port values are resolved exactly once, creating a static configuration that can be inspected and validated before containers start.
Conflict Detection and Resolution
The resolution script scans all ports: entries across the merged compose configuration and builds a mapping of host-port → service pairs. If the same host port appears for two distinct services, the script aborts immediately with a descriptive error indicating the colliding services and the offending port number.
This logic is continuously verified by the test suite dream-server/tests/test-port-contracts.sh, which simulates conflicting configurations to ensure the validation layer catches errors before they reach Docker Engine.
Localhost Binding Enforcement
Beyond port uniqueness, Dream Server forces all services to bind exclusively to 127.0.0.1. This constraint prevents accidental exposure of development services on external network interfaces and isolates the stack to the host loopback. The binding behavior is validated by dream-server/tests/test-bind-address-sweep.sh, which verifies that no service attaches to 0.0.0.0 or specific external IPs.
Practical Configuration Examples
Overriding Ports at Install Time
When the default port allocations conflict with existing system services, users can override values via environment variables at install time. The resolution script picks up these changes during the next validation pass:
# Change the dashboard UI port from default 3000 to 9090
WEBUI_PORT=9090 ./install.sh
Detecting Collisions During Development
To manually trigger the validation layer without running a full install, invoke the resolution script directly:
# Resolve the final compose file and abort if any host-port collides
scripts/resolve-compose-stack.sh
Inspecting Current Port Assignments
To view the currently configured port values (with secrets masked), use the CLI utility:
# Show the generated environment configuration
dream config show
Example Conflict Error
If two services request the same host port, the resolution script produces an error similar to:
ERROR: Port conflict detected – services "dashboard" and "open-webui" both request host port 3000.
Fix by setting distinct values in .env (e.g. DASHBOARD_PORT=3000, WEBUI_PORT=3001) and re-run the installer.
Summary
- Deterministic defaults are stored in
.env.exampleand injected via parameter substitution (e.g.,${WEBUI_PORT:-3000}) across all compose fragments. - Pre-runtime validation occurs in
scripts/resolve-compose-stack.sh, which builds a host-port map and aborts on collision before Docker receives the final configuration. - Loopback isolation is enforced by binding all services to
127.0.0.1, preventing external interface conflicts as verified bytest-bind-address-sweep.sh. - Environment overrides allow users to resolve conflicts ad-hoc by setting variables (e.g.,
WEBUI_PORT=9090) before running./install.sh. - Automated testing via
test-port-contracts.shensures the conflict detection logic remains robust against regression.
Frequently Asked Questions
How does Dream Server detect port conflicts before containers start?
Dream Server runs scripts/resolve-compose-stack.sh during the installation process. This script merges all compose fragments (base, GPU overlays, and extensions), substitutes environment variables, and scans the resulting configuration for duplicate host ports. If it finds the same port assigned to two different services, it aborts with an error message before invoking Docker.
Can I change port numbers after the initial installation?
Yes. Modify the corresponding variable in your .env file (e.g., change WEBUI_PORT=3000 to WEBUI_PORT=9090) and re-run ./install.sh. The resolution script will re-validate the entire stack with the new values, ensuring no new conflicts are introduced by your changes.
Why does Dream Server force all services to bind to 127.0.0.1?
Binding to the loopback interface (127.0.0.1) instead of 0.0.0.0 prevents services from accidentally exposing ports on external network interfaces. This security measure, enforced by test-bind-address-sweep.sh, ensures that even if a port conflict occurred on the host's external IP, it would not affect the Dream Server stack or vice versa.
What files should I check if I encounter a port conflict error?
First, review .env.example to see the default port assignments and their variable names. Then check scripts/resolve-compose-stack.sh to understand how the merge logic processes your specific extensions. Finally, consult extensions/services/*/manifest.yaml and compose.yaml to identify which extensions are requesting the conflicting port numbers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →