Web development

Development best practices: uv, branches, clean commits and deployment

A reliable project does not depend only on the talent of whoever writes the code: it depends on what surrounds the code. An environment that rebuilds identically, a readable history, checks that run before a mistake reaches production, a deployment that does not rely on a manual operation. Here are the habits I apply in my projects, with examples from my real organisation, and what they prevent.

1. A reproducible environment with uv

The classic “it works on my machine” almost always comes from a different environment: a language version, a library updated in the meantime. For my Python projects, I use uv for everything: installing dependencies, running commands, running checks. Dependencies are pinned in a versioned lock file: two people, or two machines, get the same environment. The rule is simple and without exception for me: no manual package installation, no hand-built virtual environment.

2. The repository as the source of truth

The repository holds everything needed to rebuild and understand the project: the code, tests, documentation, deployment configuration, contribution rules. It never holds a secret: passwords, keys and environment files stay outside the repository, with an example file containing only dummy values. A secret-detection tool runs before each commit, as a last safety net.

A well-kept repository also makes the project transferable: someone else can clone it, read the README and run it.

3. Enforcing good commits: pre-commit and commitizen

A commit history is a logbook. If it is readable, you can find in seconds why a line changed. To get that without relying on everyone’s discipline, I use two tools:

  • pre-commit runs checks at every commit: formatting, code analysis, secret detection, oversized files. If a check fails, the commit does not happen.
  • commitizen enforces the message format, Conventional Commits: a type, possibly a scope, then a description (for example “feat(contact): add the form”). In my case the type is in English and the description in French.

These regular messages then serve more than reading: commitizen derives the version number from them (a new feature raises the minor version, a fix the patch version, an incompatible change the major version) and generates the changelog. And I never use the option that bypasses the checks: if a check is in the way, we fix the cause.

For collaborators this is valuable: the same rules apply to everyone, with no manual review needed to check the form, so review can focus on substance.

4. Breaking down the branches

I use the same branch flow across all my application projects:

BranchRole
mainProduction. No direct commits; it carries the version tags.
developPre-production, where we test before delivering.
feature/…, fix/…, chore/…Work in progress, one branch per subject, taken from develop.
release/…The delivery candidate, where the version number and changelog are set.
The branch flow

The cycle is as follows: work on a topic branch, merge it into develop, prepare a release branch, test it in pre-production, then merge it into main with a version tag. Each step has a single purpose, which makes going back simple. A helper script chains these operations to avoid handling mistakes, and it publishes nothing to the server without an explicit request.

5. Local checks first

Tests and code analysis run on my machine, before pushing. Feedback is immediate, and nothing goes to the server without passing the checks. In my setup, the continuous integration server is used only for deployment: it does not replace local tests. This choice limits dependence on an external service and makes mistakes cheaper: you see them in seconds, not after a queue.

6. CI/CD deployment to a server

Continuous integration and deployment (CI/CD) automate the path between validated code and the server. In my organisation:

  • merging into develop triggers a deployment to pre-production;
  • merging into main, with its version tag, triggers the deployment to production;
  • server access and sensitive settings are protected variables of the platform, never written in the repository;
  • nothing is pushed, tagged or deployed without my explicit request.

The point of automation is not speed but repeatability: a deployment always performs the same operations in the same order, which a manual operation never guarantees.

7. What these habits prevent

  • a fix pushed straight to production without testing;
  • a version that cannot be found because it was never tagged;
  • a secret published by mistake in the history;
  • differences between the development machine and the server;
  • an unreadable history where nobody knows why a line changed.

Where to start

  1. Pin the dependencies and document the launch command.
  2. Add pre-commit with only two or three checks, then add more gradually.
  3. Adopt the commit message format, even when working alone.
  4. Separate production, pre-production and work in progress.
  5. Automate deployment once the previous steps are stable.

A typical day

  1. I take a topic branch from develop with the helper script.
  2. I code, and run the tests and analysis locally.
  3. I commit with commitizen’s assistant: type, scope, description. Pre-commit checks formatting and secrets.
  4. I merge into develop: pre-production is updated by the platform.
  5. When a batch is ready, I prepare the release branch: the version and changelog are calculated from the commit messages.
  6. After validation in pre-production, merging into main and the version tag trigger production, on explicit request.

Onboarding a collaborator

These habits make a newcomer’s arrival easier. They clone the repository, run one command to rebuild the environment, enable the commit checks and follow the same flow as everyone else. The rules are not in someone’s head: they are in files, applied automatically. Code review can then focus on logic, instead of spotting extra spaces or incomprehensible commit messages.

Pitfalls to avoid

  • Too many checks at once: the team ends up bypassing them. Better a small number of fast checks that stay respected.
  • Branches that live too long: the more they diverge, the more painful the merge. Short branches, merged often.
  • Tests only on the server: you wait and lose the thread. Local checks give immediate feedback.
  • A deployment only its author understands: it must be described in the repository’s documentation.

Documenting to last

All these practices lose value if nobody can find them again. Each project therefore contains a README explaining how to install, launch, test and deploy it, as well as an automatically generated changelog. The rule I impose on myself: the documentation of a change is in the same commit as the change. It cannot be forgotten, nor go stale without that showing in the history.

In short

These practices are not reserved for large teams. Even alone, they make a project safer and easier to take over. Today they come with an assistant such as Claude, whose framework is set through versioned configuration files: I cover this in Claude in a development project.

A project to make more reliable?

If you want to set up these practices on your developments, describe your context to me: I will reply with a proposal on a quote basis.

Request a quote