Dashboard¶
The dashboard is the web face of vpcatlas. It scans your AWS account (or several), keeps the result in memory, and serves it as live diagrams, an inventory, a history of the scans and a comparison between any two of them. It is read-only toward AWS: the only thing that writes anything is the optional snapshot storage you point it at.
The dashboard ships with all three distributions. The installable binary, the Python package and the Docker image
all contain it, and vpcatlas serve starts it.
What it shows¶
- Live diagrams. The main page draws every diagram page in your browser with the diagrams.net viewer, from the very file the download buttons give you: the Global page, one page per Region, one page per VPC. A sidebar lists the pages, grouped by level.
- Inventory. Resources by type, the full inventory, the external accounts and the cross-account links that were found, and the collection gaps (calls that were denied or failed, so you know what the picture does not cover).
- History. Every scan that was kept, newest first, each with counters against the one before it (resources added, removed and changed, links added and removed, links that cross accounts). Any kept snapshot can be opened as a diagram.
- Diff between two runs. Pick two snapshots on the History page and the dashboard lists the resources added,
removed and changed (with the fields that changed, before and after) and the links added and removed, cross-account
ones first. It can also draw the pages of the newer snapshot with those changes marked, and offer that
.drawiofor download. - Downloads. Each diagram page as a draw.io file (
.drawio) or a Lucidchart file (.lucid), all pages in one.drawio, a.zipwith everything, and the inventory as JSON, Markdown or HTML.
Start it¶
vpcatlas serve --profile audit --regions eu-west-1,eu-central-1
Then open http://127.0.0.1:8080. The first scan runs at start-up; until it finishes the data routes answer
503 with Retry-After: 15 and the page says so.
Options of vpcatlas serve:
| Option | Default | Meaning |
|---|---|---|
--host |
127.0.0.1 |
Address to listen on. The container image uses 0.0.0.0. |
--port |
8080 |
Port to listen on (1 to 65535). |
--refresh-seconds |
300 |
Seconds between scans; 0 scans only once. Also read from VPCATLAS_REFRESH_SECONDS. |
--profile, -p |
credential chain | AWS profile. Repeat it to scan several accounts into one graph. |
--role-arn |
none | Role to assume. Repeat it to scan several accounts. |
--external-id |
none | ExternalId the role requires. Also read from VPCATLAS_EXTERNAL_ID. |
--session-duration |
3600 |
Seconds an assumed-role session lasts (900 to 43200). Also VPCATLAS_SESSION_DURATION. |
--role-session-name |
vpcatlas |
RoleSessionName used when assuming a role. Also VPCATLAS_ROLE_SESSION_NAME. |
--regions, -r |
every enabled Region | Regions to scan, comma separated. |
--exclude-regions |
none | Regions to leave out, comma separated. |
--concurrency, -c |
8 |
Regions scanned in parallel (1 to 32). |
--cache |
none | Serve this graph file (written by vpcatlas discover) instead of calling AWS. |
--account-names |
none | JSON file that maps account ids to labels. |
Everything else is set with VPCATLAS_* environment variables, read once at start-up (listed below). An option on
the command line wins over the matching variable. Secrets are never options: access keys come from the standard
AWS credential chain (see Credentials), and the dashboard password from VPCATLAS_AUTH_PASSWORD.
vpcatlas serve has no MFA options, because a dashboard that scans on a schedule cannot ask for a code. Use a role
without an MFA condition, or export short-lived credentials.
Credentials¶
The dashboard reads AWS the way the rest of the CLI does. Nothing is configured when the standard chain already resolves credentials:
- environment variables (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN); - shared credentials and config files, including named profiles and SSO profiles (
--profile,VPCATLAS_PROFILEorAWS_PROFILE); - an EC2 instance role, an ECS task role, a Lambda execution role;
- an EKS pod role (IAM roles for service accounts, through web identity).
To scan another account, set --role-arn (or VPCATLAS_ROLE_ARN, comma separated for several), plus
--external-id / VPCATLAS_EXTERNAL_ID when the trust policy requires one. The identity that assumes the roles is
the first profile when you give one, otherwise whatever the chain resolves. One audited account per role; the graphs
are merged. The trust policy and the permissions the role needs are in iam.md.
Refresh model¶
| Variable | Default | Meaning |
|---|---|---|
VPCATLAS_REFRESH_SECONDS |
0 |
Seconds between scans. 0 means the first scan and manual ones only. (vpcatlas serve defaults its option to 300; the container image sets this variable to 0.) |
VPCATLAS_MIN_REFRESH_SECONDS |
300 |
Floor of the automatic interval and of manual refreshes. |
VPCATLAS_REFRESH_MODE |
background; lazy in AWS Lambda |
background runs a loop in the process. lazy scans on request when the data is stale. |
VPCATLAS_SCAN_ON_REQUEST |
true |
In lazy mode, whether a request may start a scan. |
How it behaves:
- In
backgroundmode the dashboard loads the stored snapshot if there is one, scans at start-up when there is none (or it is older than the interval), and then scans every interval. The interval is never shorter thanVPCATLAS_MIN_REFRESH_SECONDS: a value of 60 acts as 300. - A scan runs at a time. The last good snapshot keeps being served while a scan runs, and also when a scan fails; the failure is shown in the status and in the log.
- Refresh now (
POST /refresh) starts a scan. It answers202withstartedorrunning, and429with aRetry-Afterheader when the last scan began less thanVPCATLAS_MIN_REFRESH_SECONDSago (after a failed scan the wait is at most 30 seconds). Inlazymode the scan runs inline and the answer isdoneor, when the scan fails,502. - In
lazymode there is no loop. A request that finds the snapshot older thanVPCATLAS_REFRESH_SECONDSfirst looks for a newer one in the snapshot storage (at most every 15 seconds), and scans only if there is none andVPCATLAS_SCAN_ON_REQUESTis true. This is the mode for AWS Lambda; seedeploy/lambda.md.
Scope of a scan:
| Variable | Default | Meaning |
|---|---|---|
VPCATLAS_PROFILE |
credential chain | Profile(s), comma separated. With VPCATLAS_ROLE_ARN the first one assumes the roles. |
VPCATLAS_ROLE_ARN |
none | Role(s) to assume, comma separated. |
VPCATLAS_EXTERNAL_ID |
none | External id the roles require. |
VPCATLAS_SESSION_DURATION |
3600 |
Assumed-role session length in seconds, 900 to 43200. |
VPCATLAS_ROLE_SESSION_NAME |
vpcatlas |
RoleSessionName, 2 to 64 characters. |
VPCATLAS_REGIONS |
AWS_REGION / AWS_DEFAULT_REGION, else every enabled Region |
Comma separated, or all. |
VPCATLAS_EXCLUDE_REGIONS |
none | Regions to leave out. |
VPCATLAS_CONCURRENCY |
8 |
Worker threads of one scan, 1 to 32. |
VPCATLAS_GRAPH_FILE |
none | Serve a graph written by vpcatlas discover instead of calling AWS. |
VPCATLAS_ACCOUNT_NAMES_FILE |
none | JSON object that maps account ids to labels. |
History and snapshot storage¶
Every scan produces a snapshot: the graph, the inventory and all the diagram files, frozen. The history keeps the last ones and computes what changed between neighbours.
| Variable | Default | Meaning |
|---|---|---|
VPCATLAS_SNAPSHOT_URI |
none | Where snapshots are kept: s3://bucket/prefix or file:///absolute/path. |
VPCATLAS_HISTORY_KEEP |
30 |
Snapshots kept; after each scan the oldest ones are deleted. At least 1. |
VPCATLAS_HISTORY_MIN_INTERVAL_SECONDS |
0 |
A scan identical to the previous one and newer than this is not added to the history. A scan that differs is always kept. 0 keeps every scan. |
Without VPCATLAS_SNAPSHOT_URI the history lives in memory (compressed) and is lost when the process stops. With it,
a restart, a new replica or a Lambda cold start serves the stored snapshot without scanning, and the history
survives.
Two forms are accepted:
file:///absolute/pathwrites under a directory. In a container, mount a writable volume there; the image's root file system is read-only in the shipped ECS and Kubernetes manifests.s3://bucket/prefixwrites under a bucket prefix. The identity running the dashboard needss3:GetObject,s3:PutObjectands3:DeleteObjectonarn:aws:s3:::bucket/prefix/*, ands3:ListBucketon the bucket (with the conditions3:prefix=prefix/*). Withouts3:ListBucket, S3 answersAccessDeniedfor an object that is not there yet and the store reports a read error.
Both write the same layout under the root:
| Key | Content |
|---|---|
vpcatlas-snapshot.json.gz |
The latest snapshot, replaced by every save. A cold start serves it. |
history/<UTC time>-<snapshot id>.json.gz |
One object per kept snapshot. The time is ISO 8601 with hyphens instead of colons (2026-10-17T10-00-00Z), so names sort by time. |
history/index.json |
The timeline entries, newest first. If it is missing or unreadable it is rebuilt from the objects, which loses the change counters of the entries. |
One writer is assumed. Two replicas that both scan would write the same keys, so with several replicas let a single
scheduled job do the scanning (set VPCATLAS_SCAN_ON_REQUEST=false, as in the Lambda setup).
Authentication¶
Set VPCATLAS_AUTH_PASSWORD to turn on HTTP Basic protection. The user name is VPCATLAS_AUTH_USER (default
atlas). Without a password the dashboard is open.
- Protected: every page, API route, download and
POST /refresh. - Not protected:
/healthz, so load balancers and probes keep working; the static assets under/static/; and the API documentation at/api/docsand/api/openapi.json, which describe the routes but carry no scan data. - The comparison is constant-time. Basic sends the password with every request, so serve the dashboard over HTTPS (a load balancer, an ingress or CloudFront in front of it).
- Every response carries
X-Content-Type-Options: nosniff,Referrer-Policy: no-referrerandX-Frame-Options: SAMEORIGIN; everything except/static/is sent withCache-Control: no-store, so topology data stays out of shared caches. The inventory page is served with a strict Content-Security-Policy.
The dashboard shows your network topology. Basic protection is the minimum; prefer a private network or an authenticating proxy in front of it.
Behind a proxy: base path¶
To serve it below a path, set VPCATLAS_BASE_PATH (for example /atlas). A leading slash is added and a trailing one
removed, so atlas, /atlas and /atlas/ are the same. Every link and URL the dashboard builds carries the prefix.
The application is created with the prefix as its root path, which is what puts the prefix on the links and on the
API description. Set the variable to the prefix the browser sees. It is not verified whether the proxy in front of the
dashboard should strip the prefix or forward it as is: check one page and one download through the proxy. In
Lambda behind an API Gateway stage, the same variable is passed to the handler as the API Gateway base path.
Health endpoint¶
GET /healthz answers 200 with {"status": "ok", "version": "<version>"} as soon as the process is up, even
before the first scan finishes. It needs no authentication. The container health check runs
python -m vpcatlas.dashboard.healthcheck, which requests http://127.0.0.1:$PORT/healthz (PORT, default 8080)
and exits 0 on a 200. Note that PORT only tells the probe where to look; the port the server listens on is
--port.
GET /api/status is the richer state the page polls: scan state (scanning or idle), refresh mode and intervals,
seconds until the next scan, the last error, the age and summary of the snapshot, the number of history entries and
the list of diagram pages.
The diagrams.net viewer¶
The pages draw diagrams with the diagrams.net viewer script, loaded by the browser from
https://viewer.diagrams.net/js/viewer-static.min.js. That is the only external resource the pages load. If the
browsers that open the dashboard cannot reach that host (an isolated network), the pages show "the diagrams.net
viewer did not load" after 15 seconds; the tables, the inventory and all the downloads still work.
To self-host the script:
- Download
viewer-static.min.jsfrom the diagrams.net viewer (https://viewer.diagrams.net/js/viewer-static.min.js) and publish it on a static host you control: an internal web server, an S3 bucket behind CloudFront, or a path on the reverse proxy in front of the dashboard. - Set
VPCATLAS_VIEWER_URLto its address. It must be anhttps://URL or an absolute path (starting with/); anything else is refused at start-up.
An absolute path is resolved against the dashboard's own origin, so it works when the proxy in front of the dashboard serves that path. The dashboard does not serve the file itself. Which extra resources the viewer fetches at run time has not been checked, so test the self-hosted copy in a browser with the network tab open.
HTTP routes¶
Paths are relative to VPCATLAS_BASE_PATH. Unless noted, every route needs the Basic credentials when
VPCATLAS_AUTH_PASSWORD is set. Data routes answer 503 with Retry-After: 15 until the first scan has succeeded.
| Method and path | Answers |
|---|---|
GET / |
The main page: diagram viewer, inventory, external accounts, collection gaps. |
GET /inventory |
The inventory as a standalone HTML page. |
GET /history |
The timeline and the comparison page. Query: from, to (snapshot ids). With more than one snapshot it compares the newest with the one before. |
GET /diff/{older_id}/{newer_id} |
The viewer on the pages of newer_id, with what changed since older_id marked. |
GET /healthz |
Liveness: {"status": "ok", "version": ...}. No authentication. |
GET /api/status |
Scan state, freshness, history count and diagram pages. |
GET /api/overview |
Summary detail the page loads when the snapshot changes. |
GET /api/graph |
The network graph of the current snapshot, as JSON. |
GET /api/inventory |
The inventory of the current snapshot, as JSON. |
GET /api/diagrams |
Every diagram page in tab order, with the hash, size and URL of its .drawio. |
GET /api/diagrams/{key} |
One page; the xml field is the body of its export, byte for byte. |
GET /api/history |
Every recorded snapshot, newest first, each with counters against the one before it. |
GET /api/history/{snapshot_id} |
One recorded snapshot and the .drawio files the history can still serve. |
GET /api/diff |
What changed between two snapshots. Query: from, to; to defaults to the newest, from to the one before to. If from is newer than to the diff is reversed. |
GET /api/docs, GET /api/openapi.json |
The interactive API description. No authentication. |
GET /export/{name}.drawio |
A diagram page as draw.io. name is a page key: global, a Region, or a VPC id (vpc-...); all joins every page in tab order. The SHA-256 of the file is the ETag. |
GET /export/{name}.lucid |
A diagram page as Lucidchart. |
GET /export/all.zip |
One archive: every page as .drawio and .lucid, all pages in one .drawio, the inventory in JSON, Markdown and HTML, and graph.json. |
GET /export/inventory.json, .md, .html |
The inventory in each format. |
GET /export/history/{snapshot_id}/{name}.drawio |
The .drawio of a recorded snapshot. |
GET /export/diff/{from_id}/{to_id}.json |
The body of /api/diff as a file. |
GET /export/diff/{older_id}/{newer_id}/{key}.drawio |
One page of the newer snapshot drawn with the changes marked. |
POST /refresh |
Starts a scan: 202 (started or running), 429 (throttled, with retry_after), or in lazy mode 200 (done) and 502 (failed). |
GET /static/... |
The page's CSS and JavaScript. No authentication. |
Downloaded files are named vpcatlas-<account id>-<key>.<extension>.
Logging¶
VPCATLAS_LOG_LEVEL sets the level of the vpcatlas loggers (DEBUG, INFO, WARNING, ERROR or CRITICAL;
default INFO). Scans, failures and snapshot handling are logged to standard error.
Deploying: deploy.md lists the guides for Docker, Compose, ECS, Kubernetes and Lambda.