Skip to content

Repository files navigation

OpenIPAM

License: GPL-3.0 Python 3.12 FastAPI PRs Welcome

OpenIPAM is an open-source, self-hosted IP Address Management (IPAM) platform — It is the source of truth for who owns which IP, in which subnet, in which site, with which VLAN, DNS record, and DHCP reservation — exposed through a polished web UI and a first-class REST API built for automation (Ansible, Terraform, CI pipelines).

Built with Python 3.12, FastAPI, PostgreSQL, and a server-rendered HTMX frontend. Every mutating action is audited; every concurrent allocation is safe by design.


✨ Features

Area What you get
IPAM core Hierarchical sections/sites, nested subnets with race-safe overlap detection, live utilization, split/merge operations
Addresses Locked "next free IP" allocation (advisory locks, concurrency-tested), states (used/reserved/dhcp/offline), bulk CSV import/export, IP history
DNS Forward + reverse zones and records, pluggable sync backends — PowerDNS API, BIND zone files, RFC2136 dynamic updates — with post-commit auto-sync
DHCP Scopes and MAC reservations, ISC DHCP + Kea config generators, live Kea REST push
Discovery Scheduled/on-demand ping sweeps, remote agent ingestion, reconciliation with an approval step
Security RBAC with section-scoped multi-tenancy, JWT + revocable scoped API tokens, full audit log
Automation Auto-generated OpenAPI docs, cursor pagination, Idempotency-Key support, rate limiting on burst-prone endpoints

📸 Screenshots

The UI gallery lives at docs/screenshots/ — dashboard, subnet tree, address grid, DNS zones, DHCP scopes, audit log. Screenshots are added as the UI matures; see the gallery's README for what to capture and how to contribute one.

🧱 Tech stack

Concern Choice
Language / Framework Python 3.12+ · FastAPI (async)
Database PostgreSQL 15+ — native inet/cidr types + GiST indexes
ORM / Migrations SQLAlchemy 2.0 (async) · Alembic
Validation Pydantic v2
Background jobs arq + Redis
Frontend Jinja2 · HTMX · Alpine.js · Tailwind CSS
Quality ruff · mypy (strict) · import-linter · pytest
Tooling uv · Docker · GitHub Actions

🚀 Quickstart

Prerequisites: Python 3.12+, uv, Docker (for Postgres/Redis), Node 20+ (only to rebuild CSS).

uv sync                 # install dependencies into .venv
cp .env.example .env    # adjust credentials (never commit real secrets)
docker compose up -d db redis
uv run alembic upgrade head
uv run uvicorn app.main:app --reload

Optional (local uv path): seed demo data (nested sections, subnets, addresses, DNS zone, DHCP scope, a section-scoped demo role) with uv run python -m scripts.seed_demo — or uv run python -m scripts.bootstrap_admin to provision the admin user. (The Docker path below covers the containerized equivalent.)

Docker (whole stack)

Prefer containers end to end? The compose file also runs the application — web server and arq worker — built from the Dockerfile, alongside Postgres and Redis. No Python or Node on the host needed to run the stack:

docker compose up -d --build                  # build app + worker, start all four services
# Re-run --build only when the Dockerfile or dependencies change.
docker compose exec app alembic upgrade head  # apply migrations (alembic ships in the image)
docker compose logs -f                        # follow app/worker logs
  • Web UI: http://localhost:8000 · API docs: http://localhost:8000/api/docs · Health: http://localhost:8000/health

  • The worker service (python -m app.worker) is included, so scheduled discovery scans and DNS/DHCP auto-sync jobs run out of the box — nothing to start separately.

  • The containers get IPAM_DATABASE_URL/IPAM_REDIS_URL from compose (the db/redis service names), so no .env is needed on this path.

  • The demo/admin scripts (scripts/) are not baked into the image, and there is no sign-up flow — so on a fresh compose stack, provision the admin user from the host against the compose DB:

    docker compose up -d db redis
    uv run python -m scripts.bootstrap_admin    # or seed_demo for demo data

    (The host runs only uv sync — the DB itself is the container's.)

CSS (only if you edit styles)

npm install && npm run build:css   # compiled output is committed; node is only needed to rebuild

⚙️ Configuration

All configuration is environment-driven via pydantic-settings — copy .env.example to .env and adjust. Key settings: IPAM_DATABASE_URL, IPAM_REDIS_URL, IPAM_JWT_SECRET (set a strong value in production), cookie/security flags, connection pool sizing, and the DNS/DHCP adapter settings (IPAM_DNS_SYNC_BACKEND, IPAM_PDNS_*, IPAM_BIND_*, IPAM_RFC2136_*, IPAM_DHCP_GENERATOR, IPAM_KEA_*).

🏛️ Architecture

A modular monolith: one deployable FastAPI service, cleanly separated by domain module, each with its own models, schemas, repository, service, router, and views.

┌──────────────────────── Web UI (Jinja2 + HTMX + Alpine + Tailwind) ─────────────────────┐
│                              REST API under /api/v1 · OpenAPI docs                        │
├────────────────────────────── FastAPI app (async) ───────────────────────────────────────┤
│  modules: auth · users · sections · subnets · addresses · vlans · discovery · dns ·      │
│           dhcp · audit · custom_fields · dashboard · search                               │
├────────────────────────────── Service layer (business logic) ────────────────────────────┤
├────────────────────────────── Repository layer (SQLAlchemy 2.0 async) ───────────────────┤
│              PostgreSQL (inet/cidr + GiST) · Redis (jobs · idempotency · rate limits)     │
└──────────────────────────── arq worker (discovery scans · DNS/DHCP sync) ─────────────────┘

Key design decisions — soft-delete policy, cursor pagination, error envelope, idempotency keys, API tokens, concurrency guarantees, the DNS/DHCP event-sync model — are documented in docs/architecture.md.

📁 Project layout

The repository is a single deployable service (modular monolith), laid out so that every domain has one clearly owned directory. The annotated tree:

openipam/
├── app/                    # the FastAPI application
│   ├── main.py             # app factory: routers, middleware, exception handlers
│   ├── config.py           # pydantic-settings, env-driven (IPAM_ prefix)
│   ├── database.py         # async engine/session setup, get_db dependency
│   ├── templating.py       # Jinja2 template wiring
│   ├── worker.py           # arq worker entrypoint (python -m app.worker)
│   ├── core/               # shared infrastructure — no business logic
│   ├── modules/            # one directory per domain module
│   ├── templates/          # Jinja2 templates, mirroring modules/
│   └── static/             # Tailwind CSS sources + vendored JS
├── alembic/                # database migrations
│   ├── env.py              # async migration runner
│   ├── script.py.mako      # migration template
│   └── versions/           # one file per schema change, in order
├── tests/
│   ├── conftest.py         # shared fixtures (DB/Redis gates, client, wipes)
│   ├── unit/               # pure logic, no DB/HTTP
│   ├── integration/        # DB-backed service & repository tests
│   └── e2e/                # full HTTP flows via the ASGI test client
├── scripts/                # one-off admin & demo tooling
├── docs/                   # architecture, runbooks, adapter checklists
└── .github/workflows/      # CI: lint, architecture, typecheck, test, release

app/ — the application

File Purpose
main.py create_app(): registers routers and middleware, wires the central exception handler, mounts static assets
config.py Settings via pydantic-settings — every value comes from env (IPAM_*); .env.example mirrors it
database.py async SQLAlchemy engine + session factory with explicit pool sizing and statement timeout, and the get_db dependency
templating.py Jinja2 environment: template/static dirs, globals, filters
worker.py WorkerSettings for the arq background worker (discovery scans, DNS/DHCP sync jobs); run with python -m app.worker

app/core/ — shared infrastructure

core/ is deliberately limited to cross-cutting infrastructure with no business rules of its own:

File Purpose
security.py password hashing, JWT encode/decode, get_current_user, API-token authentication
permissions.py RBAC require_permission(...) / require_api_token(...) dependencies
exceptions.py domain exception classes + the single central handler producing the error envelope
pagination.py cursor-based CursorPage[T] helpers (never offset/limit)
logging.py structured JSON logging with request-id correlation
events.py in-process domain event bus (publish/subscribe, used for DNS/DHCP auto-sync)
idempotency.py Idempotency-Key response cache for automation endpoints
rate_limit.py Redis fixed-window rate limiting for burst-prone endpoints

app/modules/ — domain modules

Each domain owns a self-contained module with the same internal layout — this is the golden rule of the codebase: cross-module access goes through the service layer only (import-linter enforces it in CI).

app/modules/<domain>/
├── models.py      # SQLAlchemy ORM models
├── schemas.py     # Pydantic request/response models
├── repository.py  # DB query functions (or handled in service.py for simple modules)
├── service.py     # business logic — the only thing routers/views call
├── router.py      # JSON REST API routes (/api/v1/...)
├── views.py       # HTML/HTMX page routes (only for modules with a UI)
└── exceptions.py  # module-specific domain exceptions
Module Owns
auth login, JWT access/refresh tokens, revocable scoped API tokens
users users, roles, permissions, RBAC data
sections hierarchical sites/folders; the access-control boundary
subnets CIDR blocks: nesting, overlap detection, split/merge, utilization
addresses IP records: states, locked "next free IP" allocation, bulk CSV import/export
vlans VLANs and subnet↔VLAN association
discovery scan jobs, schedules, reconciliation, remote-agent ingestion
dns zones/records + pluggable sync backends (PowerDNS, BIND, RFC2136)
dhcp scopes/reservations + ISC DHCP/Kea generators and Kea REST push
audit append-only event trail + query API/UI
custom_fields field-definition schema + validation, consumed by subnets/addresses
dashboard home/overview page (aggregates other modules' service reads)
search global subnet/address search

app/templates/ & app/static/

  • templates/ — Jinja2 templates, one directory per module mirroring app/modules/; base.html is the shared layout (nav, section switcher) and index.html the landing/dashboard page. HTMX partials are rendered by each module's views.py.
  • static/css/input.css (Tailwind source), the committed compiled css/main.css (rebuilt with npm run build:css), and vendor/htmx.min.js.

alembic/

Every schema change ships a numbered migration in versions/. env.py drives the async engine from IPAM_DATABASE_URL (never hardcoded). Migrations run as an explicit step (uv run alembic upgrade head) — never automatically on app boot.

tests/

Tier Scope
tests/unit/ pure logic (schemas, subnet math, generators, parser) — no DB/HTTP
tests/integration/ service + repository behavior against a real test Postgres (skips gracefully when unreachable)
tests/e2e/ full HTTP flows through the ASGI client, including auth, permissions, and idempotency/rate-limit behavior

conftest.py provides the shared fixtures: DB/Redis availability gates, an authenticated test client, and the network-state wipe that keeps the shared test DB repeatable.

scripts/

Script Purpose
bootstrap_admin.py upsert permission codes and provision the admin role/user
seed_demo.py idempotent demo data (sections, subnets, DNS/DHCP, a scoped read-only role)
discovery_agent.py lightweight remote scan agent that pushes results to the ingest API

Scripts call service-layer functions only — same module-boundary rule as the app.

docs/

Doc Contents
architecture.md working rules, full architecture, per-module roadmap status
backup-restore.md pg_dump/pg_restore runbook + Alembic-consistency rules
adapter-smoke-tests.md manual verification checklists for DNS/DHCP adapters
production-checklist.md verified go-live checklist: secrets, TLS/cookies, CSRF posture, JWT defaults, ops
troubleshooting.md deep-dive fixes for common uv/compose/migration/runtime issues

Root configuration files

File Purpose
pyproject.toml project metadata (name, version, license) + ruff/mypy/import-linter/pytest/semantic-release config
uv.lock locked dependency versions — CI installs with uv sync --frozen
alembic.ini Alembic runtime configuration
.env.example template for every supported environment variable
docker-compose.yml dev stack: db, redis, app, worker
docker-compose.test.yml isolated test Postgres + Redis
Dockerfile image with two targets: app (web) and worker (arq)
package.json / package-lock.json Tailwind CSS build (npm run build:css)
.pre-commit-config.yaml local hooks mirroring CI checks
.github/workflows/ci.yml CI: lint, architecture, typecheck, test, migration-check, semantic-release
LICENSE, CONTRIBUTING.md, README.md, .gitignore project housekeeping

🧪 Testing

DB-backed integration/e2e tests need a Postgres; with Docker:

docker compose -f docker-compose.test.yml up -d
TEST_DATABASE_URL=postgresql+asyncpg://ipam:ipam@localhost:5433/ipam uv run pytest

Without a DB reachable, DB-gated tests skip and the rest still runs: uv run pytest.

CI runs the full suite (including DB + Redis-backed tests) against service containers on every push/PR.

✅ Quality gates (mirrored in CI and pre-commit)

uv run ruff check .
uv run ruff format --check .
uv run mypy app/
uv run lint-imports      # module-boundary contract
uv run pytest

🛠️ Troubleshooting

Common problem Quick fix
uv: command not found Install uv (astral.sh/uv) and restart your shell.
uv sync --frozen fails (lockfile mismatch) Run uv sync and commit the regenerated uv.lock.
Port 5432 in use / db won't start Another Postgres owns the port; stop it or remap it in docker-compose.yml (the test stack uses 5433).
alembic upgrade head errors / missing columns A migration is missing — see the migrations section of the guide.
DB-backed tests skip The test DB is down — docker compose -f docker-compose.test.yml up -d, then run with TEST_DATABASE_URL=postgresql+asyncpg://ipam:ipam@localhost:5433/ipam uv run pytest.
Background jobs (discovery scans, DNS/DHCP sync) never run Redis or the worker is down — see the worker section of the guide.
Can't log in / cookies don't stick IPAM_COOKIE_SECURE=true needs HTTPS; bootstrap_admin won't reset an existing user's password.

The full Troubleshooting guide covers every topic in depth — environment, lockfile, Docker/services, migrations, auth, the arq worker, testing, CSS, and pre-commit — with commands for each fix.

Still stuck? Open an issue with the failing command and its output.

📈 Roadmap & status

Foundation, Auth, Users/Permissions, Sections, Subnets, VLANs, Addresses, Audit, Discovery, DNS, and DHCP milestones are complete, including the dashboard, global search, API tokens, idempotency, and rate limiting. The full per-module status table lives in docs/architecture.md (Part 3).

🤝 Contributing

Contributions are welcome! Read CONTRIBUTING.md for the development setup, coding conventions, and PR checklist. Releases are versioned automatically from Conventional Commits via semantic-release.

📄 License

OpenIPAM is free software released under the GNU General Public License v3 — see LICENSE. You may redistribute and/or modify it under the terms of the GPLv3.

About

OpenIPAM is an open-source, self-hosted IP Address Management (IPAM) platform

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages