shikamaru

Deep Dive — Docker Build & Compose

This document explains the complete Docker path: detection, unified compose generation, build, start/health, logs, and teardown.

Detection

  • Repos marked as Docker mode (globally or per-repo) are scanned for a Dockerfile.
  • If found, a Docker service entry is prepared with ports from ports-map.json (preferred) or from EXPOSE lines.
63:86:src/modes/execution/services/DockerComposeManager.ts
if (fs.existsSync(dockerfilePath)) {
  this.logger.info(`🐳 Found Dockerfile in ${repo}`);
  const serviceName = this.slug(repo);
  if (this.portsMap[serviceName]) { ports = [`${host}:${internal}`]; }
  else { ports = await this.extractPortsFromDockerfile(dockerfilePath); }
}

For .NET projects, if a compose file exists in the repo, startup/install default to docker-compose commands:

490:521:src/modes/execution/services/FrameworkDetector.ts
const hasDockerCompose = fs.existsSync(docker-compose.yml|yaml|compose.yml|yaml)
if (hasDockerCompose) return "docker-compose up";

Unified compose generation

  • Required infra services (Redis/RabbitMQ/Postgres/TimescaleDB) are added based on env resolution.
  • App services (detected Dockerfiles) are added with build.context, ports, depends_on, networks, volumes.
  • Result is serialized to docker-compose.unified.yml in the current working directory.
176:205:src/modes/execution/services/DockerComposeManager.ts
if (requiredInfraServices && requiredInfraServices.size > 0) {
  // add infra services with healthchecks, volumes, ports
}
311:329:src/modes/execution/services/DockerComposeManager.ts
unifiedCompose.services[service.name] = { build, ports, depends_on, ... };
const composeContent = yaml.dump(unifiedCompose, { indent: 2, lineWidth: 120 });
375:385:src/modes/execution/services/DockerComposeManager.ts
fs.writeFileSync(path.join(process.cwd(), "docker-compose.unified.yml"), composeContent, "utf-8");

Build

  • Services are built with:
docker-compose -f docker-compose.unified.yml build --progress=plain
  • The logger tracks steps, percent progress, pulls, and errors; suggests remedies on failures.
537:545:src/modes/execution/services/DockerComposeManager.ts
const dockerCompose = spawn("docker-compose", ["-f", composePath, "build", "--progress=plain"]);

Start & health

  • Services are started with:
docker-compose -f docker-compose.unified.yml up -d
  • Health is waited for per service (healthy status or running if no healthcheck) with timeout and heartbeats.
764:771:src/modes/execution/services/DockerComposeManager.ts
const dockerCompose = spawn("docker-compose", ["-f", composePath, "up", "-d"]);
919:986:src/modes/execution/services/DockerComposeManager.ts
await this.waitForServicesHealthy(composePath, services, { timeoutMs: 5*60*1000, intervalMs: 2000 });

Stop

  • Unified services are stopped with:
docker compose -f docker-compose.unified.yml down
999:1037:src/modes/execution/services/DockerComposeManager.ts
spawn("docker", ["compose", "-f", composePath, "down"], { stdio: "inherit" });

Logs integration

  • Each service logs are streamed via docker-compose logs -f <service> and piped into the log viewer.
1067:1079:src/modes/execution/services/DockerComposeManager.ts
spawn("docker-compose", ["-f", ..., "logs", "-f", serviceName])

Files produced

  • docker-compose.unified.yml (always when Docker/hybrid)
  • Infra-only compose may also be generated by infra manager: docker-compose.infra.yml
158:166:src/modes/execution/services/InfraServiceManager.ts
fs.writeFileSync(composePath, composeContent);

Notes & recommendations

  • Ports prefer stable host mappings from ports-map.json; falls back to EXPOSE if none.
  • Infra dependencies are wired with depends_on: { condition: service_healthy }.
  • Health waiting tolerates services without healthcheck by treating running as OK.

MIT © 2025 — shikamaru

Made with love