Bootstrapping a Terraform repository with Python, uv, and mise
Repository
Infrastructure repositories tend to get messy quite quickly.
At the beginning, the setup usually looks simple enough: a bit of Terraform, maybe a helper script or two, and a few commands written down in a README. A few weeks later, the project has Python scripts for validation or generation, different tools installed in different ways across machines, CI jobs that only partly reflect local workflows, and no clear entrypoint for the most common tasks.
That is the gap this template is meant to address. It is a starter for infrastructure-as-code repositories that use Terraform as the core, but also need some Python for backend logic, helpers, or project-specific automation. The goal is not to solve every repo design decision up front. The goal is to give the repository a clean operational shape from day one.
At a high level, the template combines a few pieces that are often needed together:
uv for Python dependency and lockfile managementmise for tool installation and task orchestrationpre-commit for consistency checks across Python, Terraform, and secretsThe repository also includes a simple Terraform layout with dev and prd environments, a setup script, a Python package under src/, tests, and a small task structure for the most common Terraform operations.
The overall structure is intentionally simple.
mise installs and manages the CLI tools the repository depends on.uv manages the Python environment and lockfile.terraform/, with separate environment-specific configuration under terraform/environments/.src/ and can be used for helper logic, validation, or backend tasks that do not fit cleanly into Terraform itself.pre-commit and GitHub Actions run the same core commands so local checks and CI stay aligned.The main value of the repository is not in having a complicated architecture. It is in drawing a clear boundary around how tools are installed, how common commands are run, and where different kinds of logic belong.
mise.tomlThis is the center of the repository.
The most opinionated choice in the template is to use mise as the main entrypoint for tools and tasks. That gives the repo one place to declare tool versions like Python, Terraform, tflint, terraform-docs, pre-commit, and uv. It also gives one place to define common commands such as formatting, linting, testing, validation, and Terraform workflows.
That matters because it reduces the number of separate conventions a project has to carry around. Instead of remembering which tools need to be installed globally and which commands are the canonical ones, the repository can point to mise run ... for the common workflow.
pyproject.toml and uv.lockPython in infrastructure repositories is often treated as an afterthought. A few scripts get added, dependencies drift, and there is no clear boundary between project code and the machine it happens to run on.
Using uv gives the Python side of the repository a proper dependency workflow from the start. pyproject.toml keeps the project metadata and Python tooling configuration in one place, while uv.lock makes dependency state reproducible. Even if the Python part of the project stays small, it is still useful to have that foundation in place.
scripts/setup.shThe setup script gives the repository a practical bootstrap path.
Its job is straightforward: install mise if needed, trust the repository configuration, install the required tools, and show the available tasks. That makes the first-run experience much better than handing someone a checklist of manual installs.
For a starter template, this is worth having. The easier it is to get from clone to working environment, the more likely the repository is to be used consistently.
.pre-commit-config.yamlThe pre-commit setup is doing real work here, not just style checks.
It covers Python formatting and linting through Ruff, Terraform formatting and validation, docs generation, and secret scanning through tools like git-secrets and gitleaks. That is a good baseline for an infrastructure repository, where a lot of damage tends to come from inconsistency or leaked credentials rather than from complex application bugs.
Starting with these checks already wired in also makes it easier to keep the repo healthy as it grows.
terraform/environments/The Terraform layout is simple and easy to reason about. Environment-specific files live under terraform/environments/<env>/, with separate configuration for backends, non-secret variables, and local secrets templates.
There are plenty of ways to structure Terraform repositories, and this is not the only valid one. But it is a sensible default for a small-to-medium project where the goal is clarity rather than maximum abstraction.
.github/workflows/ and local task alignmentOne of the better choices in the template is that CI is expected to run through the same mise run entrypoints as local development.
That sounds minor, but it removes a very common source of drift. Without that alignment, repositories often end up with one set of commands in the README, another set in local shell history, and a third set embedded in CI YAML. Using mise run as the shared layer keeps those workflows much closer together.
Plenty of infrastructure projects eventually need a bit of general-purpose logic around the edges. That might be input validation, metadata generation, helper scripts, API interactions, or other small pieces that do not fit well inside Terraform itself.
Instead of pretending those needs will never appear, this template makes space for them from the beginning. That keeps the repo from having to bolt on a half-formed Python workflow later.
mise is the top-level entrypointThis is probably the most important decision in the repository.
The repo has multiple tools with different installation stories and different commands. mise gives them a shared entrypoint. That means tool versions, environment loading, and task execution can all be defined in one place.
For a bootstrap template, that is a strong default because it lowers the amount of project-specific setup knowledge that needs to live in people’s heads.
uv for Python dependency managementuv fits well here because it is fast, straightforward, and keeps the Python side of the repository lightweight. For a repo where Python is important but not necessarily the whole product, that is a good match.
It also pairs well with the goal of keeping setup simple: dependencies are explicit, the lockfile is present, and the Python environment has a clear management path.
Infrastructure repositories benefit from guardrails early.
It is much easier to start with formatting, validation, and secret scanning already wired in than to retrofit them later once the repo is busy and inconsistent. This template takes the view that a starter should include those guardrails by default.
The intended setup flow is small enough to remember:
./scripts/setup.shmise run install.env.example to .envuv run pre-commit installFrom there, the repository exposes a set of common commands through mise run, including:
mise run fmtmise run lintmise run testmise run checkmise run tf:initmise run tf:planmise run tf:summarizemise run tf:applyThere is also a simple environment model already present, including support for commands like mise -E prd run tf:plan.
The exact command details belong in the repository README. The more important point in the context of this post is that the template already has a coherent workflow shape rather than just a collection of files.
The template is a useful foundation, but it is still a foundation.
mise, which may be unfamiliar to some teamsThose tradeoffs are mostly acceptable for a bootstrap repo. The point is not to provide the final structure for every infrastructure team. The point is to provide a clean starting point that avoids some of the most common early mistakes.
If this template evolved further, the next useful additions would probably be:
tf-uv-mise-template is a good example of the kind of repo setup work that often matters more than it looks at first glance. A clean bootstrap does not guarantee a good infrastructure project, but it does remove a lot of avoidable friction.
If you want a starting point for Terraform repositories that also need Python, shared task orchestration, and a better local-to-CI workflow story, this template is a useful place to begin.