EXPOSE, CMD and ENTRYPOINT

EXPOSE 3000 only documents the port (-P publishes it). CMD is the default command and ENTRYPOINT the executable that receives it as arguments; arguments after the image name in docker run replace CMD only. node:24-slim's entrypoint, docker-entrypoint.sh, runs real commands as given, which is why printenv worked above. Use CMD alone for applications and ENTRYPOINT for tool images. HEALTHCHECK tells Docker 514 how to test the container. Run the image against PostgreSQL 1,289 , watch its health, and run the test suite inside it:

Running the image, its healthcheck and the test suiteShell
docker network create l3-bn
docker run -d --name l3-db --network l3-bn --network-alias postgres \
  --env-file ../booknest.env postgres:18
sleep 6
docker run -d --name l3-api --network l3-bn -p 33000:3000 -e PGHOST=postgres \
  l3-booknest-api:1.0
sleep 2; docker ps --filter name=l3-api --format '{{.Names}}: {{.Status}}'
curl -s localhost:33000/ready; echo
sleep 12; docker ps --filter name=l3-api --format '{{.Names}}: {{.Status}}'
docker run --rm --network l3-bn -e PGHOST=postgres l3-booknest-api:1.0 npm test 2>&1 |
  grep -E '^(✔|ℹ (tests|pass|fail))'
Output
...
l3-api: Up 2 seconds (health: starting)
{"status":"ready"}
l3-api: Up 14 seconds (healthy)
✔ GET /health reports ok without touching the database (86.276697ms)
...
✔ GET / serves the static front end (10.973699ms)
ℹ tests 9
ℹ pass 9
ℹ fail 0

The status reads health: starting until the first check passes; --start-period forgives failures while the API connects. The check calls /health, not /ready, so a slow database never marks the API unhealthy, the distinction Kubernetes uses for liveness and readiness probes, and it uses Node.js 2,131 's built-in fetch because the image has no curl 3,008 . All nine baseline tests pass inside the image against the real database.