Two instructions decide where your code lives in the image: WORKDIR sets the directory, and COPY puts files there.
WORKDIR
WORKDIR /app- Creates
/appif it does not exist. - Every later
RUN,COPY,CMDandENTRYPOINTuses it as the current directory. - Containers start in it (
docker run --rm IMAGE pwdprints/app). - A relative
WORKDIRis relative to the previous one.
Do not use RUN cd /app. Each RUN is a new shell, so the cd is lost on the next line.
COPY
COPY <src>... <dest>
│ └─ path in the image; relative paths start at WORKDIR
└─ path in the build contextWith WORKDIR /app and this context:
~/status-page/
├── Dockerfile
├── package.json
├── server.js
└── public/style.css| Instruction | Result in the image |
|---|---|
COPY package.json ./ | /app/package.json |
COPY server.js package.json ./ | /app/server.js, /app/package.json |
COPY public/ public/ | /app/public/style.css |
COPY . . | The whole context in /app, Dockerfile included |
COPY . /usr/src/app | The whole context in /usr/src/app, not /app |
Rules that catch people out:
- A trailing
/on the destination means "into this directory". With more than one source, the destination must be a directory. - Copying a directory copies its contents, not the directory itself.
COPY public/ public/keeps the name because the destination names it. - Files are owned by root unless you pass
--chown=user:group.
does the same job with extra behaviour. Use COPY for application files.
When the code lands in the wrong place
This Dockerfile installs dependencies in /app but copies the code to /usr/src/app:
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . /usr/src/app
CMD ["node", "server.js"]The build succeeds. The container fails as soon as it starts, because CMD runs node server.js in /app:
learner@pier:~/status-page$ docker logs status
node:internal/modules/cjs/loader:1228
throw err;
^
Error: Cannot find module '/app/server.js'
at Module._resolveFilename (node:internal/modules/cjs/loader:1225:15)
...
Node.js v20.18.0Look inside the image with a throwaway container:
learner@pier:~/status-page$ docker run --rm status-page:0.3 ls
node_modules
package-lock.json
package.json
learner@pier:~/status-page$ docker run --rm status-page:0.3 ls /usr/src/app
Dockerfile
README.md
package.json
public
server.jsThe fix is one line: COPY . ., so the code joins node_modules in /app.