Plumbing and Porcelain

Plumbing and Porcelain

A metaphor from Git's documentation, useful for thinking about layered software in general. Plumbing is the low-level machinery: stable, composable, hard to use directly. Porcelain is the user-facing interface: friendly, opinionated, built on top of plumbing.

Git formally documents this split: git hash-object, git update-index, git ls-tree are plumbing. git commit, git checkout, git rebase are porcelain. Plumbing makes Git work. Porcelain makes Git usable.

The pattern beyond Git

Once you see it, it's everywhere in software architecture:

Plumbing Porcelain
Filesystem syscalls + patch format git
Git plumbing lazygit, tig, magit, gitg
HTTP libraries curl, httpie
Docker daemon API docker compose, lazydocker
Kubernetes API server kubectl, k9s
AWS REST APIs aws CLI
Compiler diagnostics LSP servers
Language Server Protocol Editor LSP clients
Debug Adapter Protocol (DAP) Lazydap, VS Code's debugger
ptrace LLDB, GDB

Every entry in the right column is a wrapper on its left-column counterpart. Wrappers all the way down.

Why it matters

Three things plumbing/porcelain enables that monolithic design doesn't:

1. Multiple porcelains per plumbing

git plumbing supports git, lazygit, tig, magit, vim-fugitive, gitg, GitHub Desktop, etc. They share the same underlying data model and don't have to coordinate with each other. Same for DAP — once you have an adapter speaking DAP, any frontend can drive it.

2. Plumbing stability

Because porcelain isolates users from plumbing details, the plumbing can evolve without breaking the entire ecosystem. Git's plumbing has been remarkably stable since the early 2000s; plumbing changes are rare and porcelain rarely needs to follow them tightly.

3. Composability

Plumbing tends to be small, single-purpose, scriptable. You can pipe plumbing commands together to do things the porcelain doesn't expose. git rev-list HEAD | xargs git cat-file — operations the porcelain doesn't have a single command for, but the plumbing makes trivial.

When plumbing is "just" plumbing

Sometimes the plumbing isn't usable on its own. DAP is plumbing; nobody opens an editor and types Content-Length: 119\r\n\r\n{"seq":1,...} by hand. ptrace is plumbing; nobody writes raw PTRACE_PEEKDATA calls in a shell to debug.

This is where the porcelain isn't optional — it's the only way users can access the plumbing's capability. Lazydap is in this position relative to DAP: DAP is unusable from a shell, lazydap exposes its capability through commands a human/agent/script can use.

The criticism "X is just a wrapper on Y" misunderstands this. X being a wrapper on Y is exactly what makes Y useful. The interesting question is what each particular wrapper lets you do that the layer below couldn't.

Where the metaphor stops

The metaphor is a bit lossy at scale: real layered systems have more than two layers. ptrace → LLDB → DAP → lazydap is four layers, each "porcelain" relative to the one below and "plumbing" relative to the one above. Plumbing and porcelain is best understood as a relative relationship between adjacent layers, not an absolute classification.

Origin

Git documentation. Linus Torvalds and early Git contributors used the term to describe the design split. See git --help for the canonical list of plumbing vs porcelain commands. The metaphor itself is older — household plumbing (pipes, valves, traps) is hidden behind porcelain (sinks, toilets, fixtures); the porcelain is what you interact with, the plumbing is what makes it work.

See also