How to reverse engineer architecture from code, the short version: work from the outside in. List what gets deployed from the infrastructure files, trace external calls from config, map internal dependencies with a graph tool, find the hotspots in git history, then validate everything against runtime traces and the humans on call. Code gives you structure. It won't give you intent.
You just inherited a repo. The README says "see the wiki." The wiki has a diagram from 2023 with a box labelled "Legacy Service (TBD)." The person who drew it left. Your first ticket touches payments, and nobody can say what else that will break.
No shame in it, this is most repos. The good news: you can rebuild a usable picture in an afternoon if you collect evidence in the right order, instead of reading files alphabetically and hoping for enlightenment.
Why reverse engineering architecture from code needs a method
Code is one witness, and it's a partial one. Each source of evidence answers a different question, and each one has a blind spot. Pretend otherwise and you'll ship a confident diagram that's confidently wrong.
| Evidence | What it proves | What it can't prove |
|---|---|---|
| Compose, Kubernetes and Terraform files | What is meant to be deployed | Whether it is what's actually running |
| Environment variables and SDK clients | Which external systems are reachable | Whether they get called, or how often |
| Import and module graphs | How code is structured | Runtime paths through dynamic dispatch or message queues |
| SQL migrations and DDL | The data model over time | Which service really owns each table |
| Git history | Where change and risk concentrate | Why the code ended up this way |
| Traces and logs | What really happens in production | Rare paths that didn't fire in your window |
So the job isn't "generate a diagram." It's cross-examining witnesses until the story holds up. Tag each box and arrow with where it came from, and you can tell facts from guesses at a glance.
→ Bring repository insight into your architecture review with Code Analysis
How to reverse engineer architecture from code in 6 steps
Step 1: Pick the question first
"Document the whole system" is how you lose a month. Pick a real question: "What breaks if we replace the payments provider?" or "Can this service move to another region?" Scope decides what you can safely ignore.
Step 2: Find what gets deployed
Skip the application code for now. Start with the files that name the deployable units:
git ls-files | grep -E '(Dockerfile|compose.*\.ya?ml|\.tf$|Chart\.yaml|kustomization\.yaml)'
Every Dockerfile is a candidate deployable. Every Terraform file is a claim about infrastructure. In C4 terms, you're collecting containers, which means applications and data stores, not Docker containers. An S3 bucket counts. A SPA counts.
If the repo uses Terraform, get the dependency view for free:
terraform init
terraform graph | dot -Tsvg > graph.svg
By default, terraform graph shows only the dependency ordering of resources and prints DOT, so you'll need Graphviz. On anything big it turns into spaghetti fast. The Terraform module visualizer gives you a more readable view of modules, resources and outputs.
Step 3: Trace the edges out of the system
External dependencies hide in config, not imports. Grep for them:
grep -rEn 'https?://|_URL|_HOST|_ENDPOINT' \
--include='*.env*' --include='*.y*ml' --include='*.tf' \
--exclude-dir=node_modules .
Then find the ways in. Look for HTTP routes, queue consumers, scheduled jobs and webhooks:
grep -rEln '@(Get|Post)Mapping|@app\.(get|post|route)|router\.(get|post)|app\.(get|post)\(' \
--exclude-dir=node_modules src
Adjust the pattern for your framework. You're after a list of entry points, not a perfect parse.
Step 4: Map the internal structure
Now you've earned the right to read code. Use a graph tool so you aren't holding the whole thing in your head. For JavaScript and TypeScript, dependency-cruiser does the job:
npx dependency-cruiser src --include-only "^src" --output-type dot \
| dot -T svg > dependency-graph.svg
Most ecosystems have an equivalent. What you're looking for is cycles, god modules that everything imports, and places where a "shared" library is actually a hidden coupling between teams. That's where your service boundaries are really drawn, whatever the org chart says.
One trap: the GitHub dependency graph isn't this. It summarizes manifest and lock files, so it tells you about packages, licenses and known vulnerabilities. Useful, but it's a supply-chain inventory, not a map of your system.
Step 5: Find hotspots in the codebase
Git history shows where the bodies are buried. List the most-changed files from the last year:
git log --format=format: --name-only --since=12.month \
| grep -v '^$' | sort | uniq -c | sort -nr | head -20
High churn plus high fan-in from Step 4 is your danger zone. Then see who actually knows that code:
git shortlog -sn --no-merges --since=12.month HEAD -- src/payments
Keep the HEAD. Without a revision, git shortlog reads from stdin when it's not attached to a terminal, so this one silently returns nothing inside a script.
Step 6: Validate against reality
Static analysis tells you what could happen. Runtime evidence tells you what does. If you have OpenTelemetry traces, follow one real request through them, since a trace shows the path a request takes through your services. No tracing? Use access logs, or just ask the on-call engineer. Twenty minutes with them beats two days of grep, and they'll tell you about the nightly job nobody committed.
→ Turn repository context into a system overview with CoDocs
Worked example: what a compose file gives away
Here's a small, made-up shop stack. It's the kind of file you'd find on day one:
services:
web:
build: ./web
ports:
- '8080:8080'
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/shop
REDIS_URL: redis://cache:6379
BROKER_URL: amqp://queue:5672
depends_on:
- db
- cache
- queue
worker:
build: ./worker
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/shop
BROKER_URL: amqp://queue:5672
PAYMENTS_API_URL: https://payments.example.com
depends_on:
- queue
db:
image: postgres:17
cache:
image: redis:7
queue:
image: rabbitmq:4
Run docker compose config first, because it resolves variables and merges overrides into the model Compose will really use. Then read it twice.
First pass, depends_on: web needs db, cache and queue. Worker needs queue. Tidy. Also incomplete, because depends_on only controls startup and shutdown order. It was never a call graph.
Second pass, environment: this is where the real story lives.
- Worker also talks to db, which
depends_onnever mentioned. So web and worker share a database, and that's a coupling worth flagging. - Worker calls an external payments API. That's the most important dependency in the file, and it's one line of config. A diagram built from
services:alone would never draw it. - Web publishes to the queue and worker consumes, probably. The direction isn't in this file. You need the code from Step 3 to confirm it.
Three findings in five minutes, and one is a real architecture smell. Start with a generated view of your own file, then annotate what it can't know. Got manifests or a schema dump instead? The Kubernetes YAML visualizer and SQL DDL to ER diagram tool do the same for those.
→ Map your compose file for free with the Docker Compose Diagrammer
Can you generate an architecture diagram from a GitHub repository?
Yes, with an asterisk. You can generate an accurate picture of what's in the repo. You can't generate what isn't in it, which includes console changes, the team next door's dependency on you, and the reason any of this exists.
| Approach | Good for | Watch out for |
|---|---|---|
terraform graph and Graphviz | Free, exact resource dependency ordering | Noisy at scale, no application view |
| dependency-cruiser or similar | Internal code structure and rule checks | Language-specific; doesn't show infrastructure |
| GitHub dependency graph | Package inventory and vulnerability info | Not an architecture view |
| Diagram as code | Versioned diagrams beside the repo | Hand-written, so it can rot like any doc |
| Architecto Code Analysis | Repository graph, hotspot views and impact context in a design review | Verify findings against runtime, like any static analysis |
Our take: don't pick one. Generated graphs are excellent evidence and terrible communication. Nobody in a design review wants 400 nodes. The output you want is a small, opinionated diagram, backed by those graphs.
That's the gap Code Analysis targets. It connects your GitHub or GitLab repository through a Git-backed analysis surface, then gives you graph, pattern and hotspot views you can use to check what a change could affect downstream. If you need the write-up too, CoDocs builds technical documentation from repository context. And when you know the shape of the system, describe it to Architect AI for an editable architecture you can refine, rather than dragging boxes for a whole afternoon.
→ Connect a repository and inspect its structure with Code Analysis
C4 diagrams from existing code: what to actually produce
Stop at two diagrams, unless you've got a reason. The C4 model has four core levels, but its own guidance is that you don't need all of them, and that context and container diagrams are enough for most teams.
| Diagram | Built from | Audience |
|---|---|---|
| System context | Steps 1 and 3: users and external systems | Everyone, including non-engineers |
| Container | Steps 2 and 3: apps, data stores, how they talk | Engineers and reviewers |
| Component | Step 4, for the area you're changing | The team doing the work |
Mark every element as confirmed or inferred, because an unmarked guess is just fan fiction with icons. A dashed line for "seen in config, not in traces" is honest, and reviewers will trust the rest more because of it.
Then write down what the code refused to tell you. The decision logs explainer is a solid starting point, because a diagram without the "why" is how you end up back here in two years. Once it's agreed, keep the picture honest: Drift Detector compares environments and IaC context to show where intent and deployed reality have diverged.
→ Draft your container diagram from a plain-English description
Reverse engineering architecture checklist
Run this before you call the diagram done:
- A single question defines the scope, and it's written on the diagram
- Every Dockerfile, chart and Terraform root is accounted for
- External systems from config appear as boxes, including SaaS and partner APIs
- Entry points are listed: HTTP, queues, schedules and webhooks
- Shared databases and shared libraries are flagged as coupling
- Circular dependencies are noted, not hidden
- Top hotspots are named, with a current owner beside each
- At least one real request is traced end to end
- Each arrow is marked confirmed or inferred
- The on-call engineer has reviewed it and found the thing you missed
- Open questions and unexplained decisions are written down
→ Turn this into a repeatable review with the Architecture Review Checklist Builder
Key takeaways
- Work outside-in: deployables, then edges, then internals, then runtime.
- Treat every source as a partial witness, and note where each box came from.
depends_onis startup order, and the GitHub dependency graph is a package list. Neither is your architecture.- Git churn plus fan-in shows you where the risk lives.
- Generated graphs are evidence. The deliverable is a small diagram with a point of view.
- Code never tells you why. Capture that before the last person who knows leaves.
→ Get code-aware context into your next design review
FAQ
How do you reverse engineer architecture from code?
Work from the outside in. List what gets deployed and what stores data from the infrastructure files, trace external calls from configuration, map internal module dependencies, find hotspots in git history, then validate the result against runtime traces and the people who run the system. Static code alone shows what could happen, not what does.
Can you generate an architecture diagram from a GitHub repository?
Partly. Dependency tools and repository analysis can produce an accurate graph of code structure, and infrastructure files can be turned into a topology view. A repository alone rarely shows live traffic, manual console changes or why a decision was made, so treat any generated diagram as a draft and validate it.
What is the fastest way to understand a new codebase?
Read the infrastructure files first, because they name the deployable units and data stores. Then find the entry points, follow one real request end to end, and check which files change most often. That gives you a working map before you read a single business rule in depth.
Does the GitHub dependency graph show my system architecture?
No. It summarizes the manifest and lock files in a repository, so it lists packages and their versions, licenses and known vulnerabilities. It doesn't describe how your own modules or services relate to each other, which is what an architecture diagram needs to show.
Which C4 diagrams should you produce from existing code?
Start with a system context diagram and a container diagram. The C4 model says those two are enough for most software development teams, and you don't need all four levels. Add a component diagram only for the area you're actively changing.
→ Generate technical documentation from your repository with CoDocs
Stop inheriting mystery systems
You can now reverse engineer architecture from code without reading every file: evidence first, small diagram second, validated by the people who get paged. The 2023 diagram finally gets to retire.
The slow part isn't drawing. It's collecting the evidence and keeping it attached to the design as the system moves. Connect your repo, get the structure in view, and take the argument to the design review instead of the wiki.