Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

copilot-docker-starter

A minimal container image built to demonstrate the Dockerfile conventions that GitHub Copilot will not apply unless you ask for them.

The application is a fifty-line HTTP server. It is trivial on purpose — the subject here is the Dockerfile.

What this demonstrates

Ask Copilot for "a Dockerfile for a Python service" in a repository with no instruction file and you will typically get a single-stage image, running as root, with CMD in shell form. Every one of those is defensible as a first draft and every one is wrong in production:

Default Consequence
Single stage Build tooling and source history ship to production
Runs as root A container escape starts as uid 0
CMD python app.py (shell form) /bin/sh does not forward SIGTERM; the container takes the full kill timeout and drops in-flight requests
FROM python:latest Rebuilds are silent upgrades
No .dockerignore COPY . . ships .git and any local .env into a readable layer

.github/copilot-instructions.md states each rule and the reason. The Dockerfile in this repository follows all of them.

Prerequisites

  • Docker with BuildKit (Docker 23 or later)

Project structure

copilot-docker-starter/
├── Dockerfile          Multi-stage, non-root, exec-form entrypoint
├── .dockerignore       A security control before it is a performance one
├── app/main.py         The trivial service
└── .github/
    ├── copilot-instructions.md
    └── instructions/docker.instructions.md   Scoped to **/Dockerfile*

Commands

docker build -t copilot-docker-starter .
docker run --rm -p 8080:8080 copilot-docker-starter
curl -s localhost:8080/health

Expected behaviour

$ curl -s localhost:8080/health
{"status": "ok"}

$ docker inspect --format '{{.Config.User}}' copilot-docker-starter
10001:10001

docker stop returns in 830ms (measured), because the entrypoint is exec form and the process handles SIGTERM correctly. Two things have to be right for that, and the second one is easy to get wrong:

  1. Exec-form ENTRYPOINT, so the process is PID 1 and receives the signal at all rather than sitting behind a /bin/sh that does not forward it.
  2. The handler must not call server.shutdown() directly. That call blocks until serve_forever() returns, and a handler interrupting serve_forever() in the same thread deadlocks waiting for itself.

The first draft of this project had (1) and not (2), and stopped in 10,499ms — indistinguishable from a container that ignores SIGTERM entirely. See app/main.py for the fix and the reasoning.

Validation

docker build -t copilot-docker-starter .
docker run --rm --read-only copilot-docker-starter python -c "print('ok')"

The image runs with a read-only root filesystem because the application is byte-compiled in the build stage and writes nothing at runtime.

Validation status: docker build is executed as part of npm run assets:check in The Copilot Stack repository. hadolint is not installed there and is not run.

Security considerations

  • No secrets in any layer, build argument or environment variable.
  • Runs as uid 10001; application code is owned by root and not writable by the running process.
  • .dockerignore excludes .git, .env*, *.pem and *.key.
  • Base image is pinned to a minor version, not latest.

Related reading

Source documentation

Licence

MIT. See LICENSE.

About

A multi-stage, non-root image. Documents the SIGTERM deadlock that made the first draft take 10,499ms to stop instead of 830ms.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages