InfraDocs Documentation
Self-hosted infrastructure documentation platform. Encrypted credential vault, full audit trail, role-based access — everything you need to document and secure your IT environment.
Multi-Site
Organize all assets by site — datacenters, offices, colo, cloud regions.
Encrypted Vault
AES-256-GCM encryption for credentials. Rate-limited reveals with full audit trail.
Audit Everything
Every create, update, delete, and credential reveal is logged with user, IP, and timestamp.
Instant Search
Full-text search across all entities with Ctrl+K. Find anything in milliseconds.
Firewall Docs
Document firewall clusters with rules, interfaces, NAT policies, and VPN tunnels.
Expiration Dashboard
Track certificate, contract, and credential expiration dates at a glance. Never miss a renewal.
Import / Export
Bulk import from CSV. Export any table to XLSX or CSV for reporting and migration.
Asset Linking
Create relationships between any assets. See what connects to what, what backs up what.
Quick Start (Try It Locally)
Want to kick the tires on your laptop before setting up a server? This gets InfraDocs running on your machine in about 2 minutes. You need Node.js 20 or higher installed — that's it.
Download InfraDocs
Open a terminal (Command Prompt, PowerShell, or Terminal on Mac) and run:
git clone https://github.com/thekennyriley/infra-docs.git
cd infra-docs
This downloads the entire project into a folder called infra-docs.
Install dependencies
Still in your terminal, run these two commands. They download the libraries InfraDocs needs:
cd backend && npm install && cd ..
cd frontend && npm install && cd ..
This takes about 30 seconds. You'll see a bunch of text scroll by — that's normal.
Start InfraDocs
You need two terminal windows — one for the backend (the brains) and one for the frontend (the UI).
Terminal 1 — start the backend:
cd backend
node server.js
You should see: InfraDocs API running on port 3001 — leave this running.
Terminal 2 — start the frontend:
cd frontend
npx vite
You should see a URL like http://localhost:3000 — leave this running too.
Open it in your browser
Go to http://localhost:3000
Log in with:
| Username | Password |
|---|---|
admin | admin |
It will ask you to set a new password — pick something you'll remember. After that, you're in! Start adding your infrastructure.
backend/data/infra.db. Encryption keys are auto-generated for dev mode. When you're ready to put this on a real server, follow the Production guide below.Production Installation
This sets up InfraDocs on a real Linux server so your team can access it 24/7. We'll walk through every single step. Total time: about 10 minutes.
What you need
- A Linux server (Ubuntu 22.04 recommended — a $5/month VPS works fine, or any spare machine)
- SSH access to that server (you should be able to run
ssh youruser@your-server-ip) - A domain name pointed at your server (optional — you can use the IP address directly)
# are explanatory comments, not commands.Step 1: Install the required software
SSH into your server and run this block. It installs Node.js (runs the app), nginx (serves it to the web), and git (downloads the code):
# Add the Node.js 22 package source
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
# Install everything
sudo apt-get install -y nodejs nginx git
# Verify it worked (you should see version numbers)
node --version
nginx -v
git --version
Step 2: Download and build InfraDocs
# Go to your home directory
cd ~
# Download InfraDocs
git clone https://github.com/thekennyriley/infra-docs.git
cd infra-docs
# Install backend libraries
cd backend && npm install --production && cd ..
# Install frontend libraries and build the UI
cd frontend && npm install && npm run build && cd ..
The npm run build step compiles the frontend into static files. You should see ✓ built in X seconds at the end.
Step 3: Generate your secret keys
InfraDocs needs two secret keys: one for login sessions, one for encrypting stored passwords. Run these two commands and save the output — you'll need it in the next step:
# Generate your JWT key (for login sessions)
echo "JWT_SECRET: $(openssl rand -hex 32)"
# Generate your encryption key (for the credential vault)
echo "ENCRYPTION_KEY: $(openssl rand -hex 32)"
Each command prints a long random string like a1b2c3d4e5f6.... Copy both.
Step 4: Create the configuration file
This tells InfraDocs where to find its database and what keys to use. Replace the placeholder values with your actual keys from Step 3:
# Create the config file
nano ~/infra-docs/.env
Paste this into the editor. Replace the two placeholder lines with your actual keys:
NODE_ENV=production
PORT=3001
DB_PATH=/home/YOUR_USERNAME/infra-docs/backend/data/infra.db
# Paste your keys from Step 3 here:
JWT_SECRET=paste-your-jwt-key-here
ENCRYPTION_KEY=paste-your-encryption-key-here
# Your server's address (change this to your domain or IP):
CORS_ORIGINS=http://your-server-ip
Save and exit nano: press Ctrl+X, then Y, then Enter.
Now lock down the file so only you can read it:
chmod 600 ~/infra-docs/.env
.env file.Step 5: Set up the auto-start service
This makes InfraDocs start automatically when your server boots and restart if it ever crashes:
sudo tee /etc/systemd/system/infra-docs.service > /dev/null << EOF
[Unit]
Description=InfraDocs Backend API
After=network.target
[Service]
Type=simple
User=$USER
WorkingDirectory=$HOME/infra-docs/backend
EnvironmentFile=$HOME/infra-docs/.env
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# Tell the system about the new service
sudo systemctl daemon-reload
# Enable it (start on boot)
sudo systemctl enable infra-docs
# Start it right now
sudo systemctl start infra-docs
# Check that it's running
sudo systemctl status infra-docs
You should see active (running) in green. If you see an error, check the Troubleshooting section.
Step 6: Set up the web server
nginx sits in front of InfraDocs and serves it to your browser. This config handles both the UI and the API:
# Create the nginx config
sudo tee /etc/nginx/sites-available/infra-docs > /dev/null << EOF
server {
listen 80;
server_name _;
# This serves the frontend UI
root $HOME/infra-docs/frontend/dist;
index index.html;
location / {
try_files \$uri \$uri/ /index.html;
}
# This forwards API requests to the backend
location /api/ {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
# Security headers
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
# Compression (makes pages load faster)
gzip on;
gzip_types text/plain text/css application/javascript application/json;
}
EOF
# Activate the config
sudo ln -sf /etc/nginx/sites-available/infra-docs /etc/nginx/sites-enabled/
# Remove the default "Welcome to nginx" page
sudo rm -f /etc/nginx/sites-enabled/default
# Test the config (should say "syntax is ok")
sudo nginx -t
# Apply it
sudo systemctl reload nginx
Step 7: Open it up!
Open your browser and go to:
http://your-server-ip
You should see the InfraDocs login page. Log in with:
| Username | Password |
|---|---|
admin | admin |
It will immediately ask you to set a new password. Pick something strong — this is your admin account.
Optional: Add HTTPS (recommended)
If you have a domain name pointed at your server, you can add free HTTPS encryption in one command:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
Certbot will ask for your email, agree to terms, and automatically configure nginx for HTTPS. It also sets up auto-renewal so your certificate never expires.
Optional: Lock down with a firewall
Only allow SSH and web traffic — block everything else:
sudo ufw allow 22 # SSH (so you can still log in)
sudo ufw allow 80 # HTTP
sudo ufw allow 443 # HTTPS
sudo ufw enable # Turn on the firewall
Dashboard
The dashboard is your at-a-glance overview of everything documented in InfraDocs. It shows:
- Asset counts — total sites, servers, network devices, storage arrays, cloud tenants, credentials, vendors, DR plans, and certificates
- Expiration warnings — certificates, vendor contracts, and credentials expiring within 30/60/90 days, color-coded by urgency (expired → critical → warning → upcoming)
- Recent backup status — last 10 backup job results
- Recent audit activity — last 20 changes across all entities
Use the dashboard as your daily check-in. If something's expired or about to expire, you'll see it here first.
Sites
Sites are the top-level organizational unit. Every asset in InfraDocs can be linked to a site.
A site represents a physical or logical location: a datacenter, office, colo facility, cloud region, or branch office.
Fields
| Field | Description |
|---|---|
name | Display name (e.g., "PHX-DC1", "AWS us-west-2") |
code | Short unique code used in naming conventions (e.g., "PHX1") |
site_type | datacenter, office, colocation, cloud, remote, branch |
address / city / state / country | Physical location details |
status | active, inactive, decommissioned |
notes | Free-form notes |
Network Assets
Document switches, routers, firewalls, access points, load balancers, VPN gateways — any network device.
Fields
| Field | Description |
|---|---|
name | Device name / hostname |
asset_type | switch, router, firewall, access_point, load_balancer, vpn_gateway, etc. |
manufacturer / model | Hardware details (e.g., Cisco, Arista, Palo Alto) |
serial_number | For warranty and support lookups |
ip_address / mgmt_ip | Production and management IPs |
firmware_version | Current firmware/OS version |
location / rack_unit | Physical location (e.g., "Row 3, Rack 12, U24-26") |
ha_role | HA role: primary, secondary, standalone |
site_id | Which site this device belongs to |
Network assets can be linked to a firewall cluster for grouped firewall documentation, and can be linked to credentials for device login details.
Firewall Clusters
Firewall clusters let you group firewall devices together and document their complete configuration: rules, interfaces, NAT policies, and VPN tunnels.
Cluster detail view
Click into any firewall cluster to see four tabs:
Rules
Document your firewall policy in order. Each rule includes:
- Rule order (sequence number)
- Name and description
- Source/destination zone and address
- Service/port and protocol
- Action (allow/deny/drop)
- Logging and enabled status
Interfaces
Document each interface with zone assignment, IP address, subnet, VLAN ID, and status.
NAT Policies
Source NAT, destination NAT, and static NAT — document the original and translated addresses/ports.
VPN Tunnels
Site-to-site and remote access VPN documentation: peer IPs, local/remote networks, IKE version, encryption settings, and tunnel status.
Networks & IPAM
The Networks page provides IP Address Management (IPAM) — document your subnets, VLANs, and individual IP assignments.
Subnets
| Field | Description |
|---|---|
name | Subnet name (e.g., "Server VLAN", "Guest WiFi") |
subnet | Network address (e.g., 10.0.1.0) |
cidr | Prefix length (e.g., 24) |
vlan_id / vlan_name | VLAN number and name |
gateway | Default gateway IP |
dns_primary / dns_secondary | DNS server addresses |
dhcp_enabled / dhcp_start / dhcp_end | DHCP scope details |
IP Addresses
Click into any subnet to manage individual IP assignments:
- IP address — the specific address
- Hostname — assigned device hostname
- MAC address — for DHCP reservations
- Assigned to — description of what's using this IP
- Status — available, assigned, reserved, deprecated
Servers & VMs
Document physical hosts, virtual machines, and containers. The Servers page includes a hierarchical view: management platforms → clusters → hosts → VMs.
Management Platforms
Document vCenter, Proxmox, Hyper-V Manager, or any hypervisor management platform with its URL, version, and license details.
Clusters
Compute clusters with HA/DRS settings, total CPU/RAM capacity, and linked management platform.
Servers
| Field | Description |
|---|---|
name | Server hostname |
server_type | physical, vm, container, hypervisor |
os / os_version | Operating system details |
ip_address | Primary IP |
cpu_cores / ram_gb | Compute specs |
cluster_id | Which cluster this server belongs to |
host_id | For VMs: which physical host they run on |
purpose / owner | What it does and who owns it |
Storage
Document SANs, NAS arrays, DAS, object storage, and hyperconverged storage.
| Field | Description |
|---|---|
name | Array/system name |
storage_type | SAN, NAS, DAS, object, cloud |
manufacturer / model | Hardware details |
capacity_tb / used_tb | Total and used capacity |
protocol | iSCSI, NFS, FC, SMB, S3 |
ip_address | Management IP |
Backup Jobs
Track all backup jobs: what's being backed up, where, how often, and whether it's working.
| Field | Description |
|---|---|
name | Job name |
backup_type | full, incremental, differential, snapshot |
software | Veeam, Commvault, rsync, AWS Backup, etc. |
source / target | What's being backed up and where to |
schedule | Cron expression or description |
retention | How long backups are kept |
rpo_hours | Recovery Point Objective |
last_run / last_status | When it last ran and whether it succeeded |
last_run and last_status regularly (or automate it via the API) to keep your dashboard accurate.Cloud & SaaS
Document cloud tenants and SaaS subscriptions: Azure, AWS, GCP, M365, Google Workspace, and any other cloud service.
| Field | Description |
|---|---|
name | Tenant/account name |
provider | Azure, AWS, GCP, M365, Google Workspace, etc. |
tenant_id | Azure tenant GUID, AWS account ID, etc. |
primary_domain | Primary domain for the tenant |
subscription_type / license_count | Subscription tier and seat count |
admin_url | Direct link to the admin portal |
Credentials
The encrypted credential vault. Store passwords, API keys, service account credentials, and SSH keys with AES-256-GCM encryption at rest.
How it works
- Passwords are encrypted before storage using AES-256-GCM with a per-secret random IV
- The encryption key (
ENCRYPTION_KEY) is never stored in the database - Passwords are masked in all list/detail views — shown as
•••••••• - To see a password, click Reveal — this requires confirmation and is rate-limited
- Every reveal is logged in the audit trail with the user, timestamp, and IP
- Revealed passwords auto-hide after 30 seconds
Fields
| Field | Description |
|---|---|
name | Credential name (e.g., "vCenter Admin", "AWS Root") |
credential_type | local, domain, service_account, api_key, certificate, ssh_key |
username | Login username |
password | Secret (encrypted at rest) |
url | Login URL or portal |
rotation_days | How often this credential should be rotated (default: 90) |
expires_at | Auto-calculated expiration date based on rotation schedule |
linked_asset_type / linked_asset_id | Link to a server, network device, or cloud tenant |
vault_path / vault_engine | For HashiCorp Vault references (pointer, not stored locally) |
Vendor Contracts
Track support contracts, software licenses, maintenance agreements, and SaaS subscriptions with their renewal dates and costs.
| Field | Description |
|---|---|
vendor_name | Vendor/company name |
contract_type | support, license, maintenance, SaaS, consulting |
contract_number | Contract/PO number |
start_date / end_date | Contract period |
annual_cost | Annual cost ($) |
renewal_type | auto, manual, multi-year |
support_level | e.g., 24x7, 8x5 NBD, Premium |
support_phone / support_email / support_portal | How to get help |
account_rep | Your account rep's name |
Contracts expiring within 90 days appear on the dashboard with urgency color-coding.
DR & Compliance
Document disaster recovery plans with RTO/RPO objectives, recovery steps, dependencies, and test schedules.
| Field | Description |
|---|---|
name | Plan name (e.g., "PHX-DC1 Full Site Failover") |
site_id | Which site this plan covers |
rto_hours / rpo_hours | Recovery Time/Point Objectives |
priority | critical, high, medium, low |
recovery_steps | Step-by-step recovery procedure |
dependencies | What must be restored first |
last_tested / next_test | DR test schedule |
Global Search
Press Ctrl+K (or ⌘+K on Mac) anywhere in the app to open the search palette.
Search is powered by SQLite FTS5 (full-text search). It indexes names, IPs, hostnames, serial numbers, descriptions, notes, and other key fields across all entity types.
Results show the entity type, name, and a highlighted snippet of the matching text. Click any result to jump directly to it.
Import / Export
Import (CSV)
- Navigate to Import in the sidebar
- Select the entity type (sites, servers, credentials, etc.)
- Upload a CSV file
- Preview the first 5 rows and map CSV columns to database fields
- Click Import — rows are inserted in a transaction (all or nothing on errors)
Maximum file size: 50MB. UTF-8 and Latin-1 encodings are supported.
Export (XLSX / CSV)
From any entity list page, you can export the data. Supported formats:
- XLSX — styled Excel workbook with formatted headers
- CSV — plain comma-separated values
Credential passwords are automatically masked as [ENCRYPTED] in exports — they are never included in plaintext.
Audit Log
The audit log records every action in InfraDocs:
- create / update / delete — all CRUD operations on any entity
- reveal_password — every time a credential secret is viewed
- import — bulk import operations with row counts
- link_create — asset relationship creation
Each entry includes: timestamp, username, user ID, action, resource type, resource ID, details (JSON), and IP address.
User Management
Admins can create, edit, and delete users from Settings.
Creating users
Set the username, display name, password, and role. New users can optionally be flagged to change their password on first login.
Changing passwords
Users can change their own password. Admins can reset any user's password. Minimum length: 8 characters.
Deactivating users
Set a user's account to inactive to revoke access without deleting them. Their audit history is preserved.
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
JWT_SECRET | Required | Dev fallback | Signs auth tokens. openssl rand -hex 32 |
ENCRYPTION_KEY | Required | Dev fallback | AES-256-GCM key for credentials. openssl rand -hex 32 |
CORS_ORIGINS | No | http://localhost:3000 | Comma-separated allowed origins |
PORT | No | 3001 | Backend API port |
DB_PATH | No | ./data/infra.db | SQLite database file path |
NODE_ENV | No | — | Set to production to enforce required secrets |
Roles & Permissions
| Permission | admin | engineer | auditor | readonly |
|---|---|---|---|---|
| View all assets | ✅ | ✅ | ✅ | ✅ |
| Create / edit assets | ✅ | ✅ | ❌ | ❌ |
| Delete assets | ✅ | ❌ | ❌ | ❌ |
| Reveal credentials | ✅ | ✅ | ❌ | ❌ |
| Import data | ✅ | ✅ | ❌ | ❌ |
| Export data | ✅ | ✅ | ✅ | ✅ |
| View audit log | ✅ | ❌ | ✅ | ❌ |
| Manage users | ✅ | ❌ | ❌ | ❌ |
Security
Encryption
- Credentials at rest — AES-256-GCM with random IV per secret. Format:
iv:authTag:ciphertext(hex-encoded) - Passwords — bcrypt hash (cost factor 10), never stored in plaintext
- Auth tokens — JWT (HS256), 24-hour expiry
Access control
- All API endpoints require authentication (Bearer JWT)
- Role-based authorization on every route
- Credential reveals rate-limited: 10/minute per user
- CORS locked to configured origins
- Forced password change on first login for new users
Audit trail
- Every mutation logged with user, action, resource, details, IP, and timestamp
- Credential reveals logged separately for compliance
- Audit log is append-only via the application (no delete endpoint)
Recommendations
- Always use HTTPS in production (Let's Encrypt + nginx or Caddy)
- Back up your
ENCRYPTION_KEYsecurely — losing it means losing all credential secrets - Rotate the admin password immediately after deployment
- Use a firewall (UFW) to restrict access to your server
- Consider forwarding audit logs to an external syslog for tamper-resistance
Backup & Restore
What to back up
Two files contain your entire InfraDocs state:
backend/data/infra.db— the SQLite database (all data).env— your encryption key and JWT secret
ENCRYPTION_KEY, all stored credential secrets are permanently unrecoverable. Store it in a password manager, offline USB, or printed in a safe.Backup command
# Copy database
cp backend/data/infra.db ~/backups/infra-docs-$(date +%F).db
# Copy env (contains your encryption key!)
cp .env ~/backups/infra-docs-env-$(date +%F)
Automated daily backup
# Add to crontab (crontab -e)
0 2 * * * cp /home/$USER/infra-docs/backend/data/infra.db \
/home/$USER/backups/infra-docs-$(date +\%F).db && \
find /home/$USER/backups -name "infra-docs-*.db" -mtime +30 -delete
Restore
sudo systemctl stop infra-docs
cp ~/backups/infra-docs-2026-03-01.db backend/data/infra.db
sudo systemctl start infra-docs
Updating
cd /home/$USER/infra-docs
# Pull latest
git pull
# Rebuild
cd backend && npm install --production && cd ..
cd frontend && npm install && npm run build && cd ..
# Restart
sudo systemctl restart infra-docs
The database schema auto-migrates on startup via safeAddColumn() — no manual migration steps needed. New tables are created with CREATE TABLE IF NOT EXISTS, and new columns are added non-destructively.
API Reference
InfraDocs exposes a RESTful JSON API. All endpoints require a Bearer JWT token (obtained from /api/auth/login).
Authentication
# Login — returns JWT token
POST /api/auth/login
{ "username": "admin", "password": "your-password" }
# Response
{ "token": "eyJ...", "user": { "id": 1, "username": "admin", "role": "admin" } }
# Use the token in subsequent requests
Authorization: Bearer eyJ...
CRUD endpoints
Every entity type follows the same pattern:
| Method | Path | Description | Role |
|---|---|---|---|
GET | /api/{entity} | List (paginated, filterable) | Any authenticated |
GET | /api/{entity}/:id | Get one (with linked assets) | Any authenticated |
POST | /api/{entity} | Create | admin, engineer |
PUT | /api/{entity}/:id | Update | admin, engineer |
DELETE | /api/{entity}/:id | Delete | admin only |
Entity paths
/api/sites · /api/network-assets · /api/servers · /api/clusters · /api/management-platforms · /api/storage · /api/backup-jobs · /api/cloud-tenants · /api/credentials · /api/vendor-contracts · /api/dr-plans · /api/ipam · /api/ip-addresses · /api/certificates · /api/firewall-clusters
List query parameters
| Param | Default | Description |
|---|---|---|
page | 1 | Page number |
limit | 50 | Items per page (max 200) |
sort | id | Sort column |
order | desc | asc or desc |
site_id | — | Filter by site |
status | — | Filter by status |
search | — | Full-text search |
Credential-specific endpoints
# Reveal a credential (rate-limited, audited)
GET /api/credentials/:id/reveal
# → { "password": "the-secret", "source": "local" }
# List credentials linked to an asset
GET /api/credentials?linked_asset_type=servers&linked_asset_id=5
Firewall cluster sub-resources
GET/POST/PUT/DELETE /api/firewall-clusters/:id/rules
GET/POST/PUT/DELETE /api/firewall-clusters/:id/interfaces
GET/POST/PUT/DELETE /api/firewall-clusters/:id/nat
GET/POST/PUT/DELETE /api/firewall-clusters/:id/vpn
Search
GET /api/search?q=web-server&type=servers&limit=20
Import / Export
# Export to XLSX or CSV
GET /api/export/servers?format=xlsx
GET /api/export/credentials?format=csv
# Import preview (returns column headers + first 5 rows)
POST /api/import/preview # multipart: file
# Import with column mapping
POST /api/import/servers # multipart: file + mapping JSON
Asset links
# Create a link between two assets
POST /api/links
{ "source_type": "servers", "source_id": 1, "target_type": "storage", "target_id": 3, "link_type": "uses" }
# Get all links for an asset
GET /api/links/servers/1
Troubleshooting
App won't start in production
FATAL: JWT_SECRET environment variable is required in production.
Set JWT_SECRET and ENCRYPTION_KEY in your .env file and make sure the systemd service uses EnvironmentFile=.
Frontend shows blank page
# Verify the frontend is built
ls frontend/dist/index.html
# Verify nginx config
sudo nginx -t
# Check nginx is pointing to the right directory
grep root /etc/nginx/sites-enabled/infra-docs
Credential decryption fails
If you changed your ENCRYPTION_KEY after storing credentials, the old ones can't be decrypted. Always use the same key, or re-enter credentials after a key change.
Search returns no results
The FTS5 index is rebuilt automatically on create/update/delete. If it gets out of sync, restart the backend — the search index is rebuilt on boot if needed.
Check logs
# Backend logs
sudo journalctl -u infra-docs -n 50 --no-pager
# nginx logs
sudo tail -f /var/log/nginx/error.log
# What's on port 3001?
ss -tlnp | grep 3001
InfraDocs — Built by ATADAY