Skip to content

feat(compose): use compose project name as workspace name - #1006

Draft
devsy-app[bot] wants to merge 3 commits into
mainfrom
feat/compose-project-name
Draft

feat(compose): use compose project name as workspace name#1006
devsy-app[bot] wants to merge 3 commits into
mainfrom
feat/compose-project-name

Conversation

@devsy-app

@devsy-app devsy-app Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Resolves #999.

Summary

When running devsy workspace up . with the docker provider on a compose-based devcontainer, devsy ignored the configured compose project name and always used the sanitized random runner id, spawning a separate compose project instead of reusing an existing one. This PR resolves the project name following the @devcontainers/cli reference precedence so an existing compose project is reused.

Precedence (final, aligned with the reference)

  1. COMPOSE_PROJECT_NAME from the shell session.
  2. COMPOSE_PROJECT_NAME from the collected .env files (first definition wins). devsy passes these same files to docker compose --env-file, so reading COMPOSE_PROJECT_NAME from the same set keeps devsy and the runtime compose invocation consistent.
  3. The merged compose files' top-level name: (last declaration wins, matching multi-file override semantics).
  4. The sanitized random runner id (devsy's analog of the reference's directory-basename fallback).

devsy passes the resolved name via --project-name, which is Docker Compose's highest-precedence override — exactly the mechanism devsy was already using, just with the wrong value.

Comparison with the official specification

  • Dev Containers spec (containers.dev): the name field is "A name for the dev container displayed in the UI" — a display name, not a compose project name. The spec's Docker Compose section is silent on project-name derivation. So devcontainer.json name is not used.
  • @devcontainers/cli reference (getProjectName in src/spec-node/dockerCompose.ts): resolves COMPOSE_PROJECT_NAME (shell) → COMPOSE_PROJECT_NAME (<cwd>/.env) → top-level name: (with a guard distinguishing a user-declared name: from compose-go's injected default) → directory basename. devsy matches this ladder, using the random runner id as its final fallback to preserve existing behavior.
  • Docker Compose (docs.docker.com/compose/how-tos/project-name/): -p flag > COMPOSE_PROJECT_NAME env > top-level name: > project-dir basename > current-dir basename. Since devsy passes the resolved name via --project-name, step 1 (the -p flag) is what takes effect.

TopLevelComposeName reads raw compose fragments, so it only returns user-declared names — never compose-go's injected name: devcontainer default, which is the same distinction the reference's fragment re-check makes.

Known divergence from the reference

devsy reads COMPOSE_PROJECT_NAME from .devcontainer/.env and compose-dir .env files in addition to <workspace>/.env, whereas the reference reads only <workspace>/.env. This is intentional: devsy already passes all of these as --env-file to docker compose, so they are part of compose's own resolution at runtime. Reading from the same set keeps the computed name and the runtime name identical, which is what makes "reuse the existing compose project" work.

Changes

  • pkg/devcontainer/compose.go:
    • loadComposeProject no longer unconditionally overwrites the project name with the random runner id; it calls the new resolveComposeProjectName.
    • resolveComposeProjectName implements the precedence above.
    • Env-file collection is split into baseEnvFiles (devcontainer config-dir .env + workspace-root .env, used to resolve $COMPOSE_FILE and COMPOSE_PROJECT_NAME) and composeDirEnvFiles (.env files colocated with the resolved compose files, added after compose-file resolution). Deduplication preserves first-seen order.
  • pkg/compose/projectname.go (new): pure, testable helpers ProjectNameFromEnvFiles and TopLevelComposeName, plus the ComposeProjectNameEnv constant.
  • pkg/compose/helper.go: added SanitizeProjectName (exported wrapper around the existing sanitization logic) so the devcontainer layer can sanitize the random-id fallback without re-implementing version-dependent character rules.

Notes

  • stopDockerCompose / deleteDockerCompose are unaffected: they derive the project name from the running container's com.docker.compose.project label, which compose sets from --project-name at up time, so they stay consistent with the new resolution.
  • The loaded project's Name from compose-go is intentionally not trusted directly, because compose-go falls back to the project directory's base name when no explicit name source is present. Each higher-precedence source is read explicitly instead.

Testing

  • New unit tests for ProjectNameFromEnvFiles, TopLevelComposeName, and SanitizeProjectName in pkg/compose/projectname_test.go.
  • New unit tests for resolveComposeProjectName (all precedence levels, multi-file last-wins), baseEnvFiles (collection + dedup), composeDirEnvFiles, and appendEnvFiles in pkg/devcontainer/compose_projectname_test.go.
  • go vet, gofmt, golines, and golangci-lint v2.12.2 (--new-from-rev=origin/main) all clean; go build ./... passes.
  • E2e: the full Test up-docker-compose suite is green in CI, including four new specs that assert the resolved project name (top-level compose name:, COMPOSE_PROJECT_NAME from .env, COMPOSE_PROJECT_NAME from the shell, and reuse of the existing compose project on a second up). The shared findComposeContainer helper now resolves the actual compose project name from the dev container labels (rather than assuming the sanitized workspace UID) so the existing multi-service, shutdown, v2-features, and updateRemoteUserUID specs stay green alongside the new ones.
  • The only failing CI checks are Test up-workspaces (git exit-128) and Test self-update (update --dry-run network call failing instantly at 0.08s) — both environmental flakes unrelated to this change (this PR only touches compose project-name resolution and the up-docker-compose helper; neither test exercises compose). They passed on the prior commit on this same branch and fail from network/git-runner conditions, not from these code changes.

This PR was created by an AI agent (OpenHands) on behalf of the user.

Resolve the compose project name following the @devcontainers/cli reference precedence (shell COMPOSE_PROJECT_NAME, .env COMPOSE_PROJECT_NAME, top-level compose name) instead of always using the sanitized random runner id, so an existing compose project is reused. The devcontainer.json name field is not used; it is a UI display name per the spec.
@netlify

netlify Bot commented Aug 11, 2026

Copy link
Copy Markdown

Deploy Preview for devsydev canceled.

Name Link
🔨 Latest commit 01cb778
🔍 Latest deploy log https://app.netlify.com/projects/devsydev/deploys/6a84684cdc494b0008eea794

@netlify

netlify Bot commented Aug 11, 2026

Copy link
Copy Markdown

Deploy Preview for images-devsy-sh canceled.

Name Link
🔨 Latest commit 01cb778
🔍 Latest deploy log https://app.netlify.com/projects/images-devsy-sh/deploys/6a84684c2a17b000075c4e9e

@codacy-production

codacy-production Bot commented Aug 11, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 93 complexity · 4 duplication

Metric Results
Complexity 93
Duplication 4

View in Codacy

AI Reviewer: run a review on demand. To trigger the first review automatically, go to your organization or repository integration settings. AI can make mistakes. Always validate suggestions.

Run reviewer

TIP This summary will be updated as you push new changes.

@github-actions

Copy link
Copy Markdown

⚠️ This PR contains unsigned commits. To get your PR merged, please sign those commits (git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}) and force push them to this branch (git push --force-with-lease).

If you're new to commit signing, there are different ways to set it up:

Sign commits with gpg

Follow the steps below to set up commit signing with gpg:

  1. Generate a GPG key
  2. Add the GPG key to your GitHub account
  3. Configure git to use your GPG key for commit signing
Sign commits with ssh-agent

Follow the steps below to set up commit signing with ssh-agent:

  1. Generate an SSH key and add it to ssh-agent
  2. Add the SSH key to your GitHub account
  3. Configure git to use your SSH key for commit signing
Sign commits with 1Password

You can also sign commits using 1Password, which lets you sign commits with biometrics without the signing key leaving the local 1Password process.

Learn how to use 1Password to sign your commits.

Watch the demo

@devsy-app
devsy-app Bot force-pushed the feat/compose-project-name branch 5 times, most recently from 6db5734 to 4962a7a Compare August 11, 2026 19:26
devsy-app Bot and others added 2 commits August 11, 2026 19:26
Add e2e specs asserting the com.docker.compose.project label reflects the resolved project name (top-level compose name, COMPOSE_PROJECT_NAME from .env, shell COMPOSE_PROJECT_NAME) and that a second devsy up reuses the existing compose project and container.

The dev container is located by devsy's dev.containers.id label for these project-name assertions, since the project name under test is not guaranteed to equal the sanitized workspace UID.

The shared findComposeContainer helper is updated to resolve the actual compose project name from the dev container's labels (rather than assuming the sanitized workspace UID) before querying by project + service, so it stays correct now that devsy resolves the project name from COMPOSE_PROJECT_NAME / .env / the top-level compose name. This keeps the existing multi-service, shutdown, v2-features, and updateRemoteUserUID specs green alongside the new ones.

The .env-backed case writes the workspace .env at runtime because .env is gitignored and thus not part of the committed testdata.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Use compose project name as workspace name

1 participant