---
title: "Config Files"
slug: "config-files"
category: "file-organization"
tags: ["file-organization", "config-files", "pyproject", "dotenv"]
status: "stable"
last_updated: 2026-05-14
summary: "One source of truth (pyproject.toml, package.json), env-specific overrides in .env.<env>, and never commit machine-specific config."
related: ["[[file-organization/dotfiles]]", "[[file-organization/project-structure]]", "[[file-organization/naming-conventions]]", "[[coding/general-principles]]", "[[coding/git]]"]
---

## Overview

Project configuration sprawl is one of the easiest ways to bury a clean repo. Six tools, six config files, four of them duplicating each other's settings. The rules below pick a single source of truth per ecosystem, keep environment overrides predictable, ban machine-specific config from version control, and prefer code over config when both are possible. They build on [[file-organization/dotfiles]] for per-user config.

## One source of truth per ecosystem

Modern toolchains gather everything under one manifest. Use it instead of sprinkling per-tool dotfiles.

- **Python:** `pyproject.toml` holds project metadata, dependencies (PEP 621), and tool config (`[tool.ruff]`, `[tool.pytest.ini_options]`, `[tool.mypy]`). Skip `setup.cfg`, `setup.py`, `pytest.ini`, `.flake8`, and `ruff.toml` when `pyproject.toml` will do.
- **JavaScript / TypeScript:** `package.json` holds dependencies, scripts, and most tool config (`"prettier"`, `"eslintConfig"`, `"jest"`). Move to dedicated files only when the inline form gets unwieldy.
- **Rust:** `Cargo.toml` plus a single `rust-toolchain.toml`. Resist scattering build config across multiple files.
- **Go:** `go.mod`, `go.sum`, and one `golangci.yaml`.

Consolidate before adding. The next contributor should be able to read one file to understand the project's tooling, not seven.

## Avoid config sprawl: do not duplicate settings

The common failure mode is `.editorconfig` plus `.prettierrc` plus `.eslintrc` plus `pyproject.toml`, three of them disagreeing about indent width. Pick one place per setting.

- Indent style and width: `.editorconfig` only. Every modern editor reads it.
- Code style for one language: that language's formatter config (`prettier`, `ruff format`, `rustfmt.toml`).
- Lint rules: the linter's own config, inside the project manifest when possible.

When two configs claim the same setting, the developer who edits one and not the other ships an invisible bug.

## Environment overrides go in `.env.<env>`

Per-environment config (dev, staging, prod) lives in numbered or named dotenv files. Load order is predictable:

```
.env                    # committed; default values, no secrets
.env.development        # committed; dev-only defaults
.env.production         # committed; prod-only defaults
.env.local              # gitignored; per-developer overrides
.env.production.local   # gitignored; secrets that bypass the vault
```

Tools that read this layout natively: Next.js, Vite, Nuxt, Astro, FastAPI with `pydantic-settings`, Django via `django-environ`. For Node services, use `dotenv-flow` to get the same cascade.

Rule of thumb: commit defaults that are safe to share; never commit secrets. Use a `.env.example` file as the canonical list of required keys.

## Never commit machine-specific config

A config that works on the author's laptop and nowhere else is a trap.

- Absolute paths (`/Users/alice/projects/...`): forbidden. Use environment variables or repo-relative paths.
- IDE configs (`.idea/`, `.vscode/settings.json`): mostly gitignored. The exception is `.vscode/extensions.json` (recommended extensions) and `.vscode/launch.json` (shared debug configs), if the team agreed.
- OS-specific paths (`C:\Users\...` or `~/Library/...`): never. Detect at runtime via `os.path.expanduser` or `path.join(os.homedir(), ...)`.
- Personal git aliases or hooks: belong in [[file-organization/dotfiles]], not the project repo.

A `.gitignore` block for these is mandatory. See [[coding/git]] for what `.gitignore` is and is not.

## Prefer code over config when both work

A declarative config grows, sprouts conditionals, then becomes a YAML programming language. Switch to code before it gets there.

- Webpack or Vite config past ~50 lines: keep it as TypeScript, not JSON-with-comments.
- Build pipelines: a small TypeScript or Python script is more debuggable than a 200-line `Makefile`.
- Multi-environment deploy config: a typed config object in code (Pulumi, CDK) beats a stack of YAML templates.

Code can be type-checked, tested, and refactored. Config cannot. Switch when the config has more than one level of conditional or interpolation.

## JSON vs YAML vs TOML: pick by audience

- **TOML** for human-edited project metadata (`pyproject.toml`, `Cargo.toml`). Clear sections, comments, typed values.
- **YAML** for tools that demand it (Kubernetes, GitHub Actions). Watch for indentation footguns.
- **JSON** for machine-generated config (`package.json`, lockfiles). No comments, strict syntax.
- **JSON with comments (`jsonc`)** when an ecosystem (TypeScript, VS Code) standardizes on it. Avoid otherwise.

Do not invent a fourth format. Each has tooling; a custom format does not.

## Validate config at startup, fail loudly

Config bugs that surface mid-request are the worst kind. Validate at process startup with a schema and crash on failure.

```python
# Python with pydantic-settings
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    openai_api_key: str
    request_timeout_seconds: int = 30

settings = Settings()  # raises on missing required keys
```

TypeScript: `zod` or `valibot` to parse `process.env` into a typed object; see [[coding/typescript-runtime-validation]]. Rust: `serde` + `figment`. A service that boots with garbage config is harder to debug than one that refuses to boot.

Pair this with a checked-in `.env.example` listing every required key with a placeholder value. A new contributor copies it to `.env.local`; CI diffs it against the keys the code reads to catch drift.

## Related

- [[file-organization/dotfiles]]
- [[file-organization/project-structure]]
- [[file-organization/naming-conventions]]
- [[coding/general-principles]]
- [[coding/git]]
