Keep DB seed sources outside the code tree
When a project ships content (Markdown, JSON, CSV…) as DB seed, where those files live is a surprisingly big decision. The obvious place — inside the app folder that consumes them (frontend/myapp/content/) — turns out t…
Table of contents
When a project ships content (Markdown, JSON, CSV…) as DB seed, where those files live is a surprisingly big decision. The obvious place — inside the app folder that consumes them (frontend/myapp/content/) — turns out to be the worst long-term home. Putting them at monorepo root is almost always right.
1. What goes wrong inside the app folder
| Incident | Root cause |
|---|---|
| Seed walker scans both v1 and v2 folders → duplicate inserts | Migration source and SSOT cohabit |
| Seed files end up in the app image → larger container | Bundled with app code |
| Editing seed triggers an app rebuild | Next/Vite watch content/ |
| docker-compose mount points into the app dir → confuses operators | Blurs content ≠ code |
2. Why monorepo-root placement wins
my-monorepo/
├── notes/ ← seed SSOT (monorepo root)
│ ├── backend/
│ │ ├── 01-...ko.md
│ │ └── 01-...en.md
│ └── frontend/...
├── courses/ ← seed SSOT
│ └── <series>/...
├── frontend/
│ └── admin/ ← app (seeder walks ../../notes)
└── docker-compose.yml (../../notes:/notes:ro mount)
At root:
- Folder structure itself enforces content ≠ code
- docker mounts read cleanly: content =
/notes, code =/app - Editing seed triggers zero app rebuilds
- After v1 → v2 migration, you can simply delete the v1 folder without touching app code
3. Clean up dead paths after migration — in one shot
The most common slip when adopting this pattern is leaving the v1 folder around "just in case." If you do:
- Seeder walks v1 + v2 → duplicates or gaps
- New contributors can't tell which is SSOT
- The v1 mount in
docker-compose.ymlbecomes a stale dead mount
The right fix is in one go: git rm -rf + delete the mount line + delete helper functions + sync 5 docs. Miss any one of those five spots and the next contributor gets confused.
4. Before / after
| Aspect | Inside app folder | At monorepo root |
|---|---|---|
| Content ↔ code boundary | Blurry | Folder-level separation |
| Build trigger on edit | Triggered | Not triggered |
| Container image size | Includes seeds | Seeds mounted at runtime |
| Cleanup after v1 → v2 | Easy to leave behind | One-shot git rm |
| SSOT clarity for new joiners | "Which one?" | Root is explicit |
5. Adopting this in your own project
- If content lives in
app/content/orsrc/seed/, move it to monorepo root. - Update the seeder to walk relative to
process.cwd() + ../../. - Unify docker-compose mounts to the form
../../notes:/notes:ro. - When v1 → v2 migration is complete, delete v1 folder + mount + helper in one commit.
- State "content SSOT = root
/notes/courses" in README and folder-structure docs.
Code and content are assets with different change cadences. Things that change on different cadences should live in different folders — that's the lowest operational cost.
More in data
All in this category →Related posts
Orchestrating multiple PostgreSQL pools
An admin console or back office may need one process to access separate content, catalog, and operations databases. Direct pools can be useful, but connection budgets, permissions, and failure isolation must be designed…
Spring Multi-Module (Gradle)
When several Spring applications live in a single repository, we have to settle the location of shared code, the build unit, and the direction of dependencies. Gradle's multi-project build is the standard tool for handl…
Git Submodule · Subtree · LFS — repos inside repos
When a repo needs to carry another repo's code, or when large binary files have to live inside a repo, the options that come to mind are Submodule, Subtree, Sparse Checkout, and Git LFS. Each has different strengths and…
Monorepos and lightweight tool choices
A monorepo keeps the source for many projects in a single version-controlled repository. This article covers what a monorepo is, its history, how far a lightweight setup like pnpm workspaces can take us, and what full-s…