Skip to content

A second checkout cannot take over the containers silently - #15

Merged
zelti merged 1 commit into
mainfrom
fix/two-checkouts-collide
Aug 24, 2026
Merged

zelti merged 1 commit into
mainfrom
fix/two-checkouts-collide

Conversation

@zelti

@zelti zelti commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Every clone uses the same compose project name, so starting from a second directory adopts and reconfigures the first one's containers. Seen for real on this machine while testing an upgrade:

php-devforge test-devforge
domain phpforge.dev pepito.dev
projects a scratch folder ~/Projects/pepito

The running containers mounted the first. pepito.dev stopped serving, the files were untouched, and nothing said a word. A docker compose up -d from the second directory did it — the installer was never involved, which is why the fix could not live there alone.

What is actually shared

Worth separating, because it decided the design:

  • Code is a bind mount of a real folder. Never at risk, in any scenario.
  • Database data is a named volume labelled com.docker.compose.project: php-devforge — it belongs to the project name, not to a directory.

Deriving the project name from the checkout would isolate the two, but it orphans every existing volume: the user's databases would look deleted. So the two stay one environment reachable from two folders, and what changes is that taking over is announced instead of silent.

[!] These containers were started from:
      /tmp/second-checkout
    Continuing will reconfigure them from here.
    To go back:  cd /tmp/second-checkout && forge start

    Take them over? [y/N]

forge --force <command> skips the question; with no terminal it refuses and names the flag. forge status now says which folder owns the running containers. With no conflict, nothing is printed at all.

Detection stores nothing new

Compose already labels every container with project.working_dir. active_dir() in lib/compose.sh reads it through compose_with_env, so the installer can call it before .env exists — otherwise the project name falls back to the directory name and finds nothing.

The check sits in compose_up(), which #10 made the single route every start goes through, plus cmd_stop — stopping someone else's containers destroys nothing but is just as surprising.

A claim in the plan was wrong

The plan said install.sh silently repointed the forge symlink with ln -sfn. It does not — that line only runs when the link is absent or already points here, and there is an explicit "points somewhere else. Left alone." branch. The symlink on this machine had been repointed by hand during local verification. One less change.

CI reproduces it

A real second directory, started from, then taken back, with a trap restoring ownership however the step ends. Verified that removing check_owner makes it fail with forge took over without being asked.

Two things the local run caught that CI would not have: a second clone has no gitignored docker-compose.local.yml, so the copied .env has to drop it from COMPOSE_FILE; and forge status | grep -q dies of SIGPIPE under pipefail — the same trap as forge profile on tools | grep in #10.

Every clone uses the same compose project name, so starting from a second
directory adopts and reconfigures the first one's containers. That is how
a test install stopped serving its own domain here, with nothing said.

The two stay one environment reachable from two folders: the shared name
is also what keeps database volumes working, and deriving it from the
directory would orphan every existing one. What changes is that taking
over is announced. forge asks, --force skips the question, and with no
terminal it refuses and names the flag. forge status says which folder
owns the running containers.

Detection needs nothing stored: compose already labels containers with
project.working_dir. active_dir() reads it through compose_with_env so
the installer can call it before .env exists.

The check sits in compose_up, the single route every start goes through
since the profile work, plus cmd_stop.

CI reproduces it with a real second directory and takes ownership back
through a trap. Verified it fails when check_owner is removed.
@zelti
zelti merged commit 2f0fa85 into main Aug 24, 2026
4 checks passed
@zelti
zelti deleted the fix/two-checkouts-collide branch August 24, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant