TL;DR: A volume is mounted over the directory your Dockerfile populated, hiding the image files. Remove the VOLUME instruction (or the -v flag) covering that path, or copy the files at container start via an entrypoint script. Volumes always hide whatever was in the image at that path; that is by design.

## The error

No error text; the symptom is an empty directory at runtime despite a successful COPY:

```text
$ docker run myimage ls /app
(empty)
```

while the Dockerfile contains `COPY . /app` and the build log shows it succeeding.

## Fix it

1. Confirm a volume covers the path:
   `docker inspect myimage --format '{{.Config.Volumes}}'`
   Expected: shows `map[/app:{}]` or similar.
2. Check your run command for `-v ...:/app` too; either source shadows the content.
3. Fix options:
   - Delete the `VOLUME /app` line from the Dockerfile and rebuild (image files become visible).
   - Or keep the volume and seed it at runtime: entrypoint script copies defaults into /app only if empty.
4. Rebuild and verify `docker run myimage ls /app` lists files.

## When this applies
- Dockerfile has both COPY and VOLUME for the same path
- `docker run -v myvol:/app` over a populated /app

## When this does NOT apply
- COPY itself failed (build error, different skill)
- Files missing because of .dockerignore (build-time exclusion)

## Versions
All Docker versions; VOLUME semantics are ancient and stable.

## Why it happens
Mounts overlay the image filesystem. When the container starts, the volume (empty on first use) is mounted at /app, hiding the image layer's /app entirely. Named volumes get one special case: on FIRST use with an empty volume, docker copies the image content in, but anonymous volumes and subsequent runs do not.

## Edge cases
- The named-volume auto-copy only happens when the volume is brand new AND the mount is a named volume; `docker run -v /app` (anonymous) never copies.
- Databases rely on this shadowing intentionally (VOLUME /var/lib/mysql); the bug pattern is doing it for app code or config.
- `docker compose` recreating volumes: `down -v` wipes the seeded copy too.
