A Discord Server in Git
We don't click our infrastructure together by hand. So we stopped clicking together our team's chat server, too.
There is a particular kind of software you are supposed to assemble by hand. A Discord server is the canonical example. You click "create channel," you drag roles into an order, you toggle permission checkboxes one at a time, and when you are finished you have a configuration that exists nowhere but in the running service and your memory of having made it. Six months later someone asks why a channel is private, and nobody can say.
We don't run our infrastructure that way, so this week we stopped running our chat that way. Atelium's team server is now defined in a file, in git, and a small program reconciles the live server to match it.
What "as code" means here
The whole server is one declarative config: roles, categories, channels, who can see what. A provisioner reads the live server, compares it against the file, and applies only the difference. Point it at an empty server and it builds everything. Run it a second time and it says, simply, in sync.
That second run is the entire point. Discord has no "desired state" endpoint — every tool that manages it, ours included, has to read what exists, decide what should change, and make exactly those changes and nothing else. So the heart of the thing is a pure function with a deliberately boring signature:
// Given the live server and the file, compute the difference.
function reconcile(current: ServerState, desired: Config): Action[]
Everything else — logging in, creating a channel, denying a permission — is plumbing around that function. Because it is pure, we can test the hard part (does it notice a renamed role? an extra channel? nothing at all?) without ever touching Discord. The plumbing we trust to a single live dry run that prints the plan before a thing happens.
The decision that mattered
The one choice worth keeping: every channel is hidden by default. The everyone role can see nothing; the team role can see everything. That sounds austere for a three-person server, and today it is. But it means the day we invite a client into a single project channel, that is one line — grant them their category — and there is no path for them to accidentally see the rest. The safe thing and the easy thing become the same thing, which is the only arrangement that survives a busy week.
There is a smaller lesson folded in, too. A bot cannot own the server it manages, and in Discord a role's position outranks even administrator rights — so a human still has to do two clicks no script is allowed to. Worth knowing before you assume automation can do all of it.
Why bother, for a chat server
Because it will drift. Someone makes a channel at 11pm, someone loosens a permission to unblock a call, and a month later the server is a place nobody designed. Code is how a shared space stays honest: the file is the intent, the dry run is the diff, and "in sync" is a real answer to the question "is this still what we meant?"
It is the same reason this website is flat files and the database is git. A room you can rebuild from a file is a room you actually understand. Ours rebuilds in about twenty seconds, and we will never again squint at a channel wondering why it is private.
If that is the kind of thing you like to read about, subscribe below. We don't email often, and when we do it will be worth opening.