# Ship it with Docker tips

> Local containers only help when they can see the files developers are actually editing.

Docker becomes useful when it removes a class of local setup problems instead of adding a private
runtime that only one person understands.

The goal is not to have containers for their own sake. The goal is to make development, QA, CI, and
deployment close enough that the team can script the important parts instead of remembering them.
When that works, a teammate can rebuild the environment, run the checks, and understand where the
files, ports, and generated artifacts live.

On macOS and Windows with Boot2Docker, the Docker daemon lived inside a small VM. Mounting a project
directory into a container was not just a `-v $(pwd):/app` decision. The VM had to see the host
filesystem first.

```mermaid
flowchart LR
  A[developer laptop] --> B[Boot2Docker VM]
  B --> C[Docker daemon]
  C --> D[container]
  A -. files edited here .-> E[/project source/]
  E -->|sshfs mount| B
  B -->|volume mount| D
```

One practical workaround was `sshfs` inside Boot2Docker:

```bash title="boot2docker-sshfs.sh"
boot2docker ssh
tce-load -wi sshfs-fuse
mkdir ~/osx
sudo sshfs "$username@$ipaddress:/Users/$username/" /home/docker/osx/
```

Then the container could mount a path from `/home/docker/osx/...` instead of pretending the VM had
native access to the host directory.

<Principle title="Local runtime reality matters">
  A dev environment is not correct because the app starts once. It is correct when source files,
  volumes, ports, permissions, rebuilds, and checks behave the same way for the next teammate.
</Principle>

That is the real value of Docker in a team. It is not there to make the setup feel more impressive.
It is there to remove a class of local drift. A teammate should not need to know which VM owns the
daemon, where the files are mounted, which path the container sees, and why a code change does not
appear until someone restarts a hidden service. Those details become delivery drag when they live
only in a person's memory.

The same idea is shorter in a modern Compose file:

```yaml title="compose-volume.yml"
services:
  web:
    image: example/web
    working_dir: /app
    volumes:
      - .:/app
    ports:
      - '3000:3000'
```

But the review questions are the same:

- does the mounted path match the path the app actually watches;
- does the file watcher behave on the host OS;
- are generated files written somewhere safe;
- can the same command run in CI or a preview worker;
- does the setup survive being rebuilt from scratch.

<Tradeoff title="Scripts beat tribal setup">
  A little setup code can feel like overhead until the second machine needs it. After that, the
  script is cheaper than a Slack thread, a half-working laptop, and a teammate quietly losing a day.
</Tradeoff>

I would also connect this to today's agent workflows. An agent can inspect a `docker-compose.yml`,
a `Makefile`, an Ansible role, or a README check. It cannot inspect a habit somebody keeps in their
head. If the local system matters to shipping, put the important parts where tools and people can
read them.

The point is simple: encode the repeat path, keep the setup small, test the boring parts, and make
local work close enough to production that the team gets useful feedback before the deploy.
