NeonClary Cursor commited on
Commit
2011737
·
1 Parent(s): 69d90be

build: BuildKit cache mounts + .dockerignore + cursor rule

Browse files

Dockerfile opts into BuildKit (# syntax=docker/dockerfile:1.7) and mounts persistent caches at /root/.npm and /home/user/.cache/pip so npm and pip downloads survive across rebuilds. First build is unchanged; subsequent rebuilds skip the network for unchanged dependencies.

.dockerignore narrows the build context (drops .git, .cursor, node_modules, build outputs, Python/Node caches, .env files, backend/tests, etc.) so the daemon does not ship hundreds of MB of local-dev cruft to the builder on every build.

.cursor/rules/docker-build.mdc captures the conventions (syntax directive, cache mount targets, no --no-cache-dir, build context narrowing) so future agents reuse the same pattern instead of regressing.

Co-authored-by: Cursor <cursoragent@cursor.com>

Files changed (3) hide show
  1. .cursor/rules/docker-build.mdc +87 -0
  2. .dockerignore +61 -0
  3. Dockerfile +23 -2
.cursor/rules/docker-build.mdc ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ description: Docker build cache conventions for CCAI-Vibe-Demo
3
+ globs: Dockerfile,docker-compose*.yml,.dockerignore,backend/requirements.txt,frontend/package*.json
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Docker build cache conventions
8
+
9
+ This project uses **BuildKit cache mounts** so `npm ci` and `pip install`
10
+ skip the network when only source files change. Don't replace them
11
+ with plain `RUN` commands - that reintroduces full re-downloads on every
12
+ dependency change.
13
+
14
+ ## Required Dockerfile pattern
15
+
16
+ The Dockerfile MUST start with the BuildKit syntax directive:
17
+
18
+ ```dockerfile
19
+ # syntax=docker/dockerfile:1.7
20
+ ```
21
+
22
+ ### Frontend stage (root user, node:22-alpine)
23
+
24
+ ```dockerfile
25
+ COPY frontend/package.json frontend/package-lock.json ./
26
+ RUN --mount=type=cache,target=/root/.npm \
27
+ npm ci
28
+ ```
29
+
30
+ ### Backend stage (USER user, uid 1000, python:3.12-slim)
31
+
32
+ ```dockerfile
33
+ COPY --chown=user backend/requirements.txt ./
34
+ RUN --mount=type=cache,target=/home/user/.cache/pip,uid=1000,gid=1000 \
35
+ pip install --user -r requirements.txt
36
+ ```
37
+
38
+ The `uid=1000,gid=1000` flags are mandatory because the cache target
39
+ sits inside the unprivileged user's home dir. Without them the cache
40
+ is created root-owned and pip cannot write to it.
41
+
42
+ ## Don'ts
43
+
44
+ - **Don't** pass `--no-cache-dir` to `pip install`. It defeats the
45
+ cache mount. The mount lives outside the image, so wheels never
46
+ bloat the final layer either way.
47
+ - **Don't** `COPY .` blindly. Layers must be copy-deps-first,
48
+ copy-source-second so a source edit doesn't bust the deps layer.
49
+ - **Don't** add new files outside `backend/`, `frontend/` or the
50
+ lockfiles to a `COPY` line without first checking `.dockerignore`.
51
+
52
+ ## Build context (`.dockerignore`)
53
+
54
+ The ignore list excludes `.git/`, `node_modules/`, `frontend/build/`,
55
+ `agent-transcripts/`, `__pycache__`, secrets (`.env*`),
56
+ `docker-compose.override.yml`, `backend/tests/`, and editor metadata.
57
+ If you need a previously-ignored path inside the image, add a narrow
58
+ `!path/to/keep` exception rather than removing the broad ignore.
59
+
60
+ ## Verifying the cache works
61
+
62
+ After a Dockerfile or deps-file change, run two builds back to back:
63
+
64
+ ```powershell
65
+ docker compose build
66
+ docker compose build # second time
67
+ ```
68
+
69
+ The second build's `npm ci` / `pip install` step should print a
70
+ `CACHED` line (when nothing in the deps file changed) or a
71
+ near-instant `=> [internal] load build context` finish (when only
72
+ source files changed). If it re-downloads, the cache mount is
73
+ mis-configured.
74
+
75
+ If a build ever errors with `the --mount option requires BuildKit`,
76
+ opt in explicitly:
77
+
78
+ ```powershell
79
+ $env:DOCKER_BUILDKIT=1; docker compose build
80
+ ```
81
+
82
+ ## HuggingFace Spaces compatibility
83
+
84
+ HuggingFace's Docker Space builder honors the `# syntax=` directive
85
+ and BuildKit cache mounts. Older builders silently treat the mounts
86
+ as no-ops, so this Dockerfile remains forward-compatible with any
87
+ plain-Docker environment.
.dockerignore ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Build context exclusions for `docker build` / `docker compose build`.
2
+ # Trimming the context speeds the daemon transfer and keeps cache hashes
3
+ # stable: changes to ignored paths do NOT invalidate Dockerfile layers.
4
+
5
+ # ── Git / VCS metadata ───────────────────────────────────────────────
6
+ .git
7
+ .gitignore
8
+ .gitattributes
9
+ .github
10
+
11
+ # ── Cursor / agent transcripts (local IDE state, never needed in image)
12
+ .cursor
13
+ agent-transcripts
14
+ .commit-msg.txt
15
+
16
+ # ── Editor / OS junk ─────────────────────────────────────────────────
17
+ .vscode
18
+ .idea
19
+ .DS_Store
20
+ Thumbs.db
21
+ *.swp
22
+
23
+ # ── Secrets / env (image gets env at runtime via docker-compose)
24
+ .env
25
+ .env.*
26
+ *.env
27
+ !**/.env.example
28
+
29
+ # ── Python local caches ──────────────────────────────────────────────
30
+ **/__pycache__
31
+ *.pyc
32
+ *.pyo
33
+ *.pyd
34
+ *.egg-info
35
+ .pytest_cache
36
+ .mypy_cache
37
+ .ruff_cache
38
+ .venv
39
+ venv
40
+
41
+ # ── Node local caches & build outputs ────────────────────────────────
42
+ # Lockfiles ARE included on purpose so `npm ci` is reproducible.
43
+ **/node_modules
44
+ frontend/build
45
+ frontend/.next
46
+ frontend/.cache
47
+ **/.cache
48
+ npm-debug.log*
49
+ yarn-debug.log*
50
+ yarn-error.log*
51
+
52
+ # ── Local-only docker compose overrides ──────────────────────────────
53
+ # The base docker-compose.yml is needed for `docker compose build` from
54
+ # the build dir, but the local override (which references absolute host
55
+ # paths to a secrets file outside this repo) shouldn't end up in the
56
+ # image build context.
57
+ docker-compose.override.yml
58
+
59
+ # ── Tests aren't shipped in the runtime image ────────────────────────
60
+ # (Re-enable if you want to run pytest inside the deployed container.)
61
+ backend/tests
Dockerfile CHANGED
@@ -1,11 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  FROM node:22-alpine AS frontend-build
2
  WORKDIR /app/frontend
 
 
 
3
  COPY frontend/package.json frontend/package-lock.json ./
4
- RUN npm ci
 
 
5
  COPY frontend/ ./
6
  ENV REACT_APP_API_URL=
7
  RUN npm run build
8
 
 
9
  FROM python:3.12-slim
10
 
11
  RUN useradd -m -u 1000 user
@@ -16,8 +34,11 @@ ENV HOME=/home/user \
16
 
17
  WORKDIR $HOME/app
18
 
 
 
19
  COPY --chown=user backend/requirements.txt ./
20
- RUN pip install --no-cache-dir -r requirements.txt
 
21
 
22
  COPY --chown=user backend/ ./
23
  COPY --chown=user --from=frontend-build /app/frontend/build ./static
 
1
+ # syntax=docker/dockerfile:1.7
2
+ #
3
+ # BuildKit cache mounts speed up rebuilds by persisting npm and pip
4
+ # package caches across builds (outside the image - they don't bloat
5
+ # the final layer). The first `docker compose build` is unchanged;
6
+ # subsequent rebuilds skip the network for any unchanged dependency.
7
+ #
8
+ # The `# syntax=` directive auto-opts into BuildKit on Docker Desktop
9
+ # 20.10+; HuggingFace Spaces also supports this directive. Older
10
+ # builders silently treat the cache mounts as no-ops, so this file
11
+ # remains forward-compatible.
12
+
13
  FROM node:22-alpine AS frontend-build
14
  WORKDIR /app/frontend
15
+
16
+ # Copy lockfile + manifest first so the layer hash only changes when
17
+ # deps actually change, not on every source edit.
18
  COPY frontend/package.json frontend/package-lock.json ./
19
+ RUN --mount=type=cache,target=/root/.npm \
20
+ npm ci
21
+
22
  COPY frontend/ ./
23
  ENV REACT_APP_API_URL=
24
  RUN npm run build
25
 
26
+
27
  FROM python:3.12-slim
28
 
29
  RUN useradd -m -u 1000 user
 
34
 
35
  WORKDIR $HOME/app
36
 
37
+ # Lockfile-equivalent first; cache mount targets the unprivileged
38
+ # user's pip cache (uid/gid 1000 matches the `user` account).
39
  COPY --chown=user backend/requirements.txt ./
40
+ RUN --mount=type=cache,target=/home/user/.cache/pip,uid=1000,gid=1000 \
41
+ pip install --user -r requirements.txt
42
 
43
  COPY --chown=user backend/ ./
44
  COPY --chown=user --from=frontend-build /app/frontend/build ./static