Updates
How a project keeps up with the template and its dependencies, and how an existing repo adopts the template.
Keeping your project up to date
copier update performs a three-way merge against the latest template release.
User-owned files are never overwritten: README.md, CHANGELOG.md, citation files (CITATION.cff, codemeta.json), community-level governance documents, web app starter code, <package>.api.md, and go.mod.
Known limit: once a project has deleted any file the template renders, copier 9.19.1 reads the skip list before it loads the answers. A Compose-only repo's compose.yaml and .env.example, and the owned files under ts_dir or go_dir, then merge with the template's and can conflict. Keep your side of each.
Updates skip the sample code and pages (the package's __init__.py, tests/test_add.py, the web app's components, the site's pages and Worker scripts), so a sample you deleted stays deleted. So does a feature you turn on later with copier update --data: it comes without its samples, which a fresh copier copy into a scratch directory shows. Delete a file the project owns, such as README.md, tests/test_benchmarks.py or docs/og-card.html, and the update writes it again.
To move a package directory, run copier update with the new path, then delete the old directory yourself.
A project from before v0.12.0 with docs/adr/ moves its records to docs/decisions/ and removes docs/adr/.
After an update, refresh and commit the lockfiles (uv lock, pnpm install, just go-sync).
Hearing about new releases
- Renovate: Automatically opens update PRs using
copier update --skip-answered --defaults. Inspect PR diffs for conflict markers before merging. - Dependabot: The
.github/workflows/copier-update.ymlworkflow runs weekly and on demand. When updates exist, it opens a PR on branchchore/copier-update. It updates each.copier-answers*.ymlin turn, so a layered template lands in the same PR. Closing that PR unmerged skips its tag; a later tag can open a new PR.
Without the update App, copier-update.yml pushes with GITHUB_TOKEN, which can't change files in .github/workflows/: an update that does opens an issue asking for a local update instead. A PR it opens starts no checks either, because GITHUB_TOKEN events trigger no workflows. Close and reopen the PR to run them; its body says so. To open the PR at all, turn on Settings > Actions > General > "Allow GitHub Actions to create and approve pull requests". With the App, the workflow does not need it.
The update App
A GitHub App of your own gives the workflow a token for each run. The token lasts an hour, reaches only the repo it runs in, and is revoked when the job ends. One App serves all your repos.
- Create the App at https://github.com/settings/apps/new. Give it a name and any homepage URL, untick Webhook > Active, and give it the repository permissions Contents, Pull requests and Workflows, each "Read and write". Let it be installed on this account only.
- On the App's page, note the Client ID and generate a private key, which downloads a
.pemfile. - Install the App on each repo that uses the template (Install App, then Only select repositories).
- In each repo, run
just repo-settings. It creates thecopier-updateenvironment and limits it tomain, so a workflow on another branch can't read the key. (On a private repo on GitHub Free the environment cannot limit branches; see the note below the commands.) Then add the App to it:
gh variable set COPIER_UPDATE_CLIENT_ID --env copier-update --body <client-id>
gh secret set COPIER_UPDATE_PRIVATE_KEY --env copier-update <app.private-key.pem
Delete the .pem file afterwards, or keep it in your password manager to set up the next repo.
A private repo on GitHub Free has no environment secrets. There, leave --env copier-update out of both commands: the job reads repo-level ones too, but then a workflow on any branch can read the key. There the key is readable from any branch, so use one App per repo, or skip the App and use the GITHUB_TOKEN path above.
Dependency updates
dependency_bot selects your automated update tool:
- Dependabot (default): built into GitHub; updates dependencies, workflows, and hook SHAs without external apps.
- Renovate: install the Renovate GitHub App, then run:
copier update --data dependency_bot=renovate
Disable Dependabot automated security fixes (gh api -X DELETE repos/OWNER/REPO/automated-security-fixes) while leaving security alerts active.
Adopting the template in an existing repo
Each guide renders over the repo on a clean branch with copier copy --overwrite, then has
you review git diff and restore what was yours.
Adopting the template in an existing Python package
- On a clean branch, render the template:
copier copy --vcs-ref=<tag> --overwrite gh:simonvanlierde/project-templates .
Enable customise, specify module_name, and select python_layout=flat if code resides at repository root rather than under src/.
2. Review git diff for pyproject.toml. Restore dependencies, authors, and version numbers, then run just py-sync.
3. Format with uv run ruff format. For existing lint or type check findings, record baseline exceptions rather than disabling rules:
- Ruff: add existing violation codes to ignore in pyproject.toml under a # NOTE: baseline at adoption comment.
- ty: configure existing violations as "warn" under [tool.ty.rules].
4. If uv audit flags patches under a week old, add temporary exemptions under [tool.uv] in pyproject.toml (tooling).
5. Remove obsolete workflows, run just check and just hooks, and commit.
6. Ensure CHANGELOG.md contains Keep a Changelog ## [X.Y.Z] headings for release tagging.
Adopting the template in an existing web app
- On a clean branch, render over the repository:
copier copy --vcs-ref=<tag> --overwrite gh:simonvanlierde/project-templates .
Select ts_kind=web-app, your framework, and your user_data setting.
2. Review git diff:
- Remove unused starter components.
- Integrate the legal page routes and links into your src/App.tsx. Add the paths to tests/e2e/routes.ts, and set its LEGAL_LINKED_FROM to the routes that link the legal pages.
- Restore custom configurations in vite.config.ts and vitest.config.ts. Retain the template's markdown() plugin.
- Restore custom package names in package.json and wrangler.jsonc.
- Fill in all TODO markers in docs/.
3. Run pnpm install. If dependencies are newer than the one-week cooldown, exempt them in pnpm-workspace.yaml:
minimumReleaseAgeExclude:
- [email protected]
- Move inline scripts from
index.htmlinto files underpublic/to satisfyscript-src 'self'. - Run
just checkandjust ts-e2e. Remove legacy workflows replaced byts.ymland commit.
Adopting the template in an existing Compose-only repo
- On a clean branch, render over the repo:
copier copy --vcs-ref=<tag> --overwrite --data 'stacks=[docker]' gh:simonvanlierde/project-templates .
- The render keeps an existing
compose.yamland.env.example, and replaces the justfile.just checkreads.env.examplein place of.env, so give every variable a${VAR:?}needs there. - Move your own recipes into
just/local.just, which updates never touch. Name the checks you addlocal-check:just checkruns that recipe last. - The
docker-composeentry in.github/dependabot.ymlreadscompose.yamland the directories one level below it, where aninclude:usually points. A deeper Compose file needs its directory added there.copier updatekeeps that edit unless the template changes the same lines, which shows up as a conflict. - Run
just checkandjust hooks. Add the words typos flags in your own files to_typos.toml, which is yours from then on. Then commit.