# veops/cmdb: custom models, three containers and a start script that edits its own settings

> veops/cmdb is a self-hosted configuration and management database for IT resources, built in Python on MySQL, Redis and a Vue front end. Its structure is defined by custom models rather than by code, and its Docker path does more to your configuration files than a start script usually does.

**veops/cmdb** — CMDB: configuration and management of IT resources

- Repository: https://github.com/veops/cmdb
- Website: https://veops.cn/cmdb
- Stars: 2,269 · Forks: 516
- Language: Python
- License: AGPL-3.0
- Published: 2026-10-14 · Updated: 2026-10-14 · Language: en
- Canonical page: https://hysenlabs.com/projects/veops-cmdb

## Custom models decide what can exist before any asset is scanned

A configuration database earns its keep by holding the shape of an estate in configuration rather than in hand-edited tables. In veops/cmdb that shape is a model, a named container of attributes where each attribute carries its own options. The feature list names dropdown lists, font colours and computed attributes, so a field can recolour a row in the interface, restrict input to a fixed set of values, or hold a value derived from other fields rather than something a person types. Models also relate to other models, which is what lets a switch record point at its interfaces and an interface point at an address.

Topics listed for the repository include itil, cmdb, dcim, ipam and flask-restful, and that list explains why one product covers three jobs: asset inventory, network address management and data centre infrastructure management. Reading it back through resource, hierarchy and relationship views is the other half, and fine-grained permission control with a complete operation log sit alongside those views.

What the repository does not show is any model schema file, plugin package or discovery job definition. Model design is the first real task for an adopter, and it has to be finished before discovery has anywhere to put what it finds.

## Discovery, IPAM and DCIM are promised by name but never scheduled in the repo

Auto discovery is listed for six resource classes: computers, network devices, storage devices, databases, middleware and public cloud resources. Nothing in the repository shows how those jobs are triggered. The top-level entries stop at .env, docker-compose.yml, docker/, install.sh, Makefile, Makefile.variable, cmdb-api/, cmdb-ui/ and docs/, with no schedule file, no cron container and no discovery configuration key among them.

Asynchronous work does exist. The compose start command runs a Celery worker on a queue named one_cmdb_async, and the Makefile starts a second worker on acl_async, so background jobs have a home even though the job names that would land in them are absent. Budget for a system where discovery is something an operator triggers and then verifies per asset type, not a nightly sweep that quietly fills the database.

The same gap shapes expectations about access control. Access control is switched on by a text substitution at container start, and the queue named for it belongs to a separate project, veops/acl, listed among the publisher's other open source repositories. Confirm the permission layer in the version you plan to deploy.

## Three containers, two version tags, and a compose file from an unstable branch

The quick start is three steps: clone, start Compose, open the browser.

```bash
git clone https://github.com/veops/cmdb.git
docker compose up -d
```

Everything an operator needs afterwards sits in docker-compose.yml, which defines a database, a cache and the API, and the three images do not carry the same tag.

```yaml
  cmdb-db:
    image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-db:2.5
  cmdb-cache:
    image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-cache:2.5
  cmdb-api:
    image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-api:2.6.2
```

The database and cache images sit at 2.5 while the API is pinned to 2.6.2, which matches the most recent published version listed for the project. The column migration command in the API start script is what bridges that gap. Two consequences follow from the same file. The images come from a Hangzhou registry mirror rather than Docker Hub, so installs outside mainland China depend on that mirror being reachable. And you cloned the default branch to obtain this compose file, the branch the README warns can sit in an unstable state during development, so the file can drift away from the API image it pins without any change on your side.

Gating is expressed in two ways at once. The database and cache carry health checks, and the API declares what it waits for:

```yaml
    environment:
      TZ: Asia/Shanghai
      WAIT_HOSTS: cmdb-db:3306, cmdb-cache:6379
      SYSTEM_DEFAULT_LANGUAGE:  # en-US, zh-CN
```

That last variable ships empty, with en-US and zh-CN named in the comment as the accepted values, so an English deployment depends on setting it rather than inheriting a default. The database health check shows where its credentials come from:

```yaml
    healthcheck:
      test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "-p$MYSQL_ROOT_PASSWORD"]
      interval: 10s
      timeout: 5s
      retries: 5
```

The password is interpolated from the environment file loaded into that service, and a service that never reports healthy keeps the API from starting at all.

## The API container rewrites its own settings file before gunicorn starts

The start command is a shell script, and its first act is editing a Python source file inside the image.

```yaml
    command:
      - /bin/sh
      - -c
      - |
        sed -i "s#USE_ACL = False#USE_ACL = True#g" settings.py
        /wait
        flask db-setup
        flask common-check-new-columns
        gunicorn --workers=4 autoapp:app -b 0.0.0.0:5000 -D
```

Access control is not a value you set in an environment file. It is a literal string swap that only works while the exact text USE_ACL = False is present in the image's settings.py. An image that reformats that assignment, or ships a different default, starts with the flag untouched, prints no error, and comes up with the permission layer the feature list advertises effectively switched off. Read the file inside the running container before exposing anything.

The /wait call and the WAIT_HOSTS variable duplicate the health-check gating, and the duplication is not harmless. A health check that never passes blocks startup outright, while /wait sits between the substitution and the schema steps, so the order of the two mechanisms decides whether a slow database is a delay or a hang.

The worker count is fixed at 4 inside the compose command, with no variable to override it.

## Pipenv and the Makefile tune the same Celery worker differently

The alternative to three containers is a local install driven by pipenv and yarn. Its dependency target is a fixed sequence.

```bash
cd cmdb-api && \
pipenv install --dev && \
pipenv run flask db-setup && \
pipenv run flask cmdb-init-cache && \
cd .. && \
cd cmdb-ui && yarn install && cd ..
```

Note the cache priming step, pipenv run flask cmdb-init-cache, which has no counterpart in the compose path because the container starts with a prepared image. Development then runs separate processes: make api serves the backend, make ui serves the front end, and make worker starts the queues.

```bash
cd cmdb-api && pipenv run celery -A celery_worker.celery worker -E -Q one_cmdb_async --autoscale=5,2 --logfile=one_cmdb_async.log -D && pipenv run celery -A celery_worker.celery worker -E -Q acl_async --autoscale=2,1 --logfile=one_acl_async.log -D
```

Here is the detail worth catching before you choose a path. That worker uses --autoscale=5,2 and runs both queues, while the compose command starts the same worker with --autoscale=4,1 and one queue. The two documented installations therefore give you different background capacity, and the containerised one shows no worker for acl_async at all.

The environment behind them is blunt: sudo easy_install pip, then pip install pipenv from the Huawei Cloud PyPI mirror, then npm install yarn, then make deps. Running make api uses flask run -h 0.0.0.0 with no port, so the development server takes its own default, and make lint runs flake8 --exclude=env . . Both paths assume a Unix shell with sudo and system-wide package installs.

## Port 8000 in the browser, 5000 in the container, 23306 for MySQL

The quick start opens http://127.0.0.1:8000 after the containers come up, while gunicorn inside the API container binds 0.0.0.0:5000, so a published port mapping sits between the two numbers. The database publishes 23306:3306 to the host, which is where a local MySQL client should connect, using the credentials from the environment file. The cache declares no host port for 6379 as written, so Redis answers on the internal compose network only, under the alias redis, while the database is aliased mysql for the API.

Two details in the database service are easy to miss. Character set configuration arrives from two places at once:

```yaml
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
```

That command line sets utf8mb4 for the server and a matching collation, while the compose file also mounts ./docs/mysqld.cnf over /etc/mysql/conf.d/mysqld.cnf. Get the pair wrong and the result is mangled text in the interface rather than a failed start, and nothing in the compose file or the README says which setting wins. Hosts on the compose network reach the database as mysql, not as localhost, so a diagnostic query run from inside the API container needs that name.

## cmdb.sql runs once, and only against an empty data volume

The database service mounts a SQL dump into the MySQL entrypoint directory and keeps its data in a named volume beside it.

```yaml
    volumes:
      - db-data:/var/lib/mysql
      - ./docs/mysqld.cnf:/etc/mysql/conf.d/mysqld.cnf
      - ./docs/cmdb.sql:/docker-entrypoint-initdb.d/cmdb.sql
```

MySQL executes files in that directory only when the data directory is empty, so the schema lands on the first start and never again. Because db-data is a named volume, restarting the stack, or pulling a newer image tag, does not reapply the dump. A database left half migrated by a failed upgrade stays half migrated until someone removes the volume and starts over, which is why upgrades run through the API's own flask db-setup and flask common-check-new-columns commands rather than through re-importing the dump. The README does not document a rollback path and the Makefile defines no rollback target, so rehearse an upgrade against a copy of the volume.

## Default demo credentials sit next to an environment file you must create first

Both the database and the API service load the .env file, and the health check of the database reads $MYSQL_ROOT_PASSWORD from it. If that file is absent, or the variable is empty, mysqladmin pings with no password, the service never reports healthy, and the dependency condition leaves the API container waiting without a useful message. The symptom is an API that never starts, not a clear configuration error, which is worth knowing before anyone starts debugging gunicorn.

The first thing to change is the login. The quick start and the public demo site both use the account name demo or admin with the password 123456, and those defaults exist in the images you just started. A stack reachable on any address other than 127.0.0.1 is an inventory system anyone can read until you change them. The repository is published under AGPL-3.0, which is the licence identifier to read before modifying anything, and what that means for a modified build served over a network is a question for your own legal review rather than something this project answers.

Nothing in the repository describes a backup or restore procedure for the db-data volume either, so keep your own copy.

## Conclusion

This fits teams that already run Compose, want a self-hosted inventory with IP address and data centre records in one place, and can afford to design their own model tree before any discovery runs. It does not suit an engineer who wants a read-only asset list, since the stack wants MySQL, Redis and a schema migration step before it serves anything. Before deploying, change the demo and admin passwords the images ship with, confirm inside the running container that the ACL flag really flipped on in settings.py, and copy the db-data volume before trying another image tag. The last push was on 2026-07-25.

## FAQ

### What are the default login credentials for veops/cmdb?

The quick start lists demo or admin as the username with 123456 as the password, and the public demo site at cmdb.veops.cn uses the same pair. Change them before putting the stack on any address other than localhost.

### How do I start veops/cmdb with Docker Compose?

Clone the repository, run docker compose up -d from the main directory, then open http://127.0.0.1:8000 in a browser. Docker and Docker Compose v2 have to be installed first.

### Which Python versions does veops/cmdb support?

The backend is listed as Python 3.8 to 3.11, with MySQL and Redis for storage and Vue.js with Ant Design Vue in the front end. The Makefile path also needs pipenv and yarn.

### Does veops/cmdb include IPAM and DCIM as well as asset inventory?

Yes. IP address management and data centre infrastructure management are listed as integrated alongside custom models, auto discovery for six resource classes, permission control and operation logs.

### Is the default master branch of veops/cmdb a safe thing to run?

The project warns that master can sit in an unstable state during development and points users at the published stable versions instead. The last push on that branch was on 2026-07-25.

### How do I reset or rebuild the veops/cmdb database?

Removing the db-data named volume is what makes MySQL run cmdb.sql again, because entrypoint scripts execute only against an empty data directory. Restarting the stack or re-pulling an image tag re-applies nothing.

## Sources

- [License: AGPL-3.0](https://github.com/veops/cmdb/blob/master/LICENSE)
- [Project website](https://veops.cn/cmdb)
- [README](https://github.com/veops/cmdb/blob/master/README.md)
- [Releases](https://github.com/veops/cmdb/releases)
- [veops/cmdb on GitHub](https://github.com/veops/cmdb)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/veops-cmdb
