Categories
AI Linux

Documenting Linux Servers from the Terminal with Claude Code

Use Claude Code in the terminal to inspect Linux servers and auto-generate Markdown docs of running services, ports and containers, version-controlled in Git.

Ask five engineers what is running on a given Linux server and you will likely get five different answers, none of which match the box itself. Documentation drifts the moment someone edits a config file, restarts a service, or ships a new container image. Claude Code sitting at the terminal of a live host, reading rather than changing, can close that gap without adding another wiki page nobody updates.

I use Claude Code to inspect servers, draft what it finds as Markdown, and commit the result to Git. The commit history becomes the drift log. No agent framework, no API key to manage, just the CLI running commands and writing files, the same way I would work through a host by hand, only faster and more consistent.

What Claude Code is, and what it needs

Claude Code is Anthropic’s command-line agent. It runs in a terminal, reads files, runs shell commands, and edits code, all under your explicit control. For this use case, that means it can log into a host’s shell, run inspection commands, and write documentation without needing a bespoke integration.

Installation is a single line: curl -fsSL https://claude.ai/install.sh | bash. If you prefer a Node-based install, npm install -g @anthropic-ai/claude-code works on Node 22 and above. Signed apt, dnf, and apk repositories are also available, which matters if you manage packages that way across a fleet. Once installed, claude –version confirms the binary, and claude doctor runs through diagnostics if something is not behaving.

Claude Code needs an account behind it: a Pro, Max, Team, or Enterprise plan, or an API provider such as Bedrock, Vertex, or Microsoft Foundry. The free Claude.ai plan does not include CLI access, so budget for that before you plan a fleet-wide rollout.

None of this needs a bespoke agent, a message queue, or a dashboard. It is a terminal tool that runs the same commands you would type yourself, which is precisely why it fits documentation work. Servers already live in a shell. Putting the documentation step in that same shell, rather than in a separate scanning tool, keeps the process close to how the work already happens.

Permissions before you point it at production

By default, Claude Code asks permission before it runs a Bash command or edits a file. That default is correct and worth keeping for anything touching production. Scope what it can do with –allowedTools rather than approving prompts one at a time, and rather than switching permissions off.

For host inspection, I keep the flag narrow and confirmed-good:

--allowedTools "Bash(systemctl:*),Bash(ss:*),Bash(docker ps:*),Read"

That covers reading service state, listening ports, and running containers, plus reading files, without opening up write access or arbitrary shell execution. Commands such as crontab -l and journalctl are still useful during an interactive session. I simply do not add them to the allowed-tools flag until I have watched Claude use them safely across enough hosts to be confident.

There is a flag that removes the permission prompts entirely: –dangerously-skip-permissions. It exists for sandboxed CI pipelines and nothing else. Do not run it against a production host.

Inspecting a host

Before Claude touches a host, I want it looking at the same handful of things I would check by hand.

Command What it shows
systemctl list-units –type=service –state=running Running services
ss -tlnp Listening ports and owning processes
docker ps Running containers
crontab -l Scheduled jobs for the current user
journalctl Recent logs for context on failures
Reading /etc configs and unit files Confirming intent behind what is running

With the allowed-tools flag from the section above, I start an interactive session on the host and give Claude a direct prompt:

“Inspect this server. Run systemctl to list running services, ss to show listening ports, and docker ps for containers. Read the relevant unit files and config files under /etc to understand why each one is running. Do not change anything. Summarise your findings once you are done.”

Claude works through the commands, reads the files it needs, and reports back in plain language what the host is doing and why. Nothing is written yet. This step is read-only by design, which makes it the safest place to start while you are still building trust in the process.

Claude Code running read-only commands in a terminal to inspect a Linux host and generate services.md
Figure 1 – Claude Code inspecting a running host and drafting services.md before committing anything to disk.

From inspection to Markdown in Git

Once the inspection prompt has run and I am happy with what it found, the next step asks Claude to turn its findings into a document:

“Write your findings to services.md in the current directory. Use one section per running service or container, describing what it does, its status, its listening ports if any, and its scheduled jobs if relevant. Keep the format plain and scannable.”

A typical result looks like this:

# Host: db01.internal
Last updated: 2026-07-18

## postgresql.service
Status: active (running)
Listening on: 127.0.0.1:5432
Purpose: Primary application database, confirmed via /etc/postgresql/16/main/postgresql.conf.

## nginx.service
Status: active (running)
Listening on: 0.0.0.0:443, 0.0.0.0:80
Purpose: Reverse proxy for the application tier, config at /etc/nginx/sites-enabled/app.conf.

## backup-container (docker)
Status: running
Image: registry.internal/backup-agent:1.4.2
Purpose: Nightly backup agent, triggered by cron.

Scheduled jobs (crontab -l, user: appuser):
0 2 * * *   /usr/local/bin/run-backup.sh

That file goes straight into Git alongside the rest of the fleet’s documentation. The next time Claude inspects the same host, it reads the existing services.md, compares it against what it now sees, and updates only what has changed. The diff Git shows between that pass and the last one is your drift log: a new service appearing, a port closing, a cron job disappearing. You do not have to remember to check. The history does it for you.

CLAUDE.md as standing context

CLAUDE.md is read automatically at the start of every Claude Code session, so it is the natural place to put standing context about a host or fleet: naming conventions, which services are expected, who owns what, and which inspection commands are off-limits.

To scaffold one, start an interactive session in the repository and run /init at the prompt:

$ claude
> /init

Claude reads the existing files, works out what the repository contains, and writes a first-pass CLAUDE.md. I then edit it down to what actually matters. A typical entry for a fleet-documentation repository looks like this:

# CLAUDE.md

This repository holds generated documentation for our Linux fleet.

- Each host has one file under hosts/<hostname>.md.
- Only inspect, never modify a host's configuration.
- Use systemctl, ss, docker ps and crontab -l as the primary sources.
- Preserve the existing structure of a host's file when updating it.
- Flag anything listening on a public interface that is not already documented.

Every session that starts in this repository picks that context up without me repeating it in every prompt.

Doing this across a fleet

Interactive sessions suit one host at a time. For a fleet, Claude Code’s headless mode is what makes this repeatable: claude -p “prompt” runs a single pass without a chat interface, –output-format text|json|stream-json controls how results come back, and –max-turns N caps how many steps it can take before stopping. You can also pin a specific model with -m if you want consistent behaviour across every host in a run, though I usually leave it on the default.

The mistake I made early on was inspecting hosts by pulling their raw command output back to a central machine and writing it to a scratch file before feeding it to Claude. That works, but it means unencrypted service listings, port maps, and config contents sitting in a temporary path, which undercuts the whole read-only, minimal-footprint point of the exercise. The better approach is to run Claude Code where the host already is: installed on each server, or invoked over ssh so the inspection happens on the box itself and only the finished Markdown travels back.

A plain shell loop is enough:

#!/usr/bin/env bash
set -euo pipefail

REPO_DIR="/opt/fleet-docs"
ALLOWED='Bash(systemctl:*),Bash(ss:*),Bash(docker ps:*),Read,Write(services.md)'

while read -r host; do
  echo "Documenting ${host}"
  ssh "${host}" "claude -p 'Inspect this server and update ~/services.md with what is running and why. Do not change anything else.' --allowedTools \"${ALLOWED}\" --output-format text --max-turns 8"
  scp "${host}:~/services.md" "${REPO_DIR}/hosts/${host}.md"
done < hosts.txt

cd "${REPO_DIR}"
git add hosts/
git commit -m "Fleet documentation pass: $(date -I)"
git push

Each host runs its own inspection over ssh, with Claude reading and writing on the box itself rather than shipping raw command output anywhere. The finished services.md is the only thing that travels, pulled back over the same encrypted connection with scp. One commit and one push covers the whole pass, so the fleet’s documentation history stays as a clean sequence of point-in-time snapshots rather than a stream of noisy per-host commits.

Headless Claude Code loop documenting a fleet of Linux servers into per-host Markdown files committed to Git
Figure 2 – A non-interactive loop running Claude Code read-only against each host, committing hosts/.md on every pass.

Keeping it honest

Where this earns its keep is in the moments documentation usually fails you. Running the inspection prompt before a change gives you a baseline to diff against afterwards. Running it during an incident postmortem tells you what was actually running at the time, not what the runbook claimed. Running it on a schedule turns a config audit from a quarterly scramble into something already done by the time someone asks for it.

None of that requires trusting Claude Code with more access than it needs. Keep it read-only until you have watched it behave correctly across enough hosts to be confident. Keep –allowedTools scoped to the commands you have actually verified. Let Git carry the history, because a commit log that shows exactly what changed and when is worth more than a wiki page nobody dares to edit.

The direction of travel is towards wider use rather than narrower. As the read-only pattern proves itself on inspection and documentation, the same discipline extends naturally to config audits ahead of an upgrade, to comparing a host against its peers in the same fleet, and to feeding a postmortem with a precise account of what was running rather than a reconstruction from memory. Start with one host, expand to the fleet once the pattern holds, and let the diffs do the documenting.

Leave a Reply

Your email address will not be published. Required fields are marked *