Local environment
Prerequisites
- Node.js 18+
- Docker + Docker Compose
- npm
Start the infrastructure
docker compose up -d postgres redisPostgreSQL on localhost:5432, Redis on localhost:6379.
To develop against SQL Server instead of Postgres:
docker compose -f docker-compose.test.yml --profile mssql up -d mssqland configure the API with DB_TYPE=mssql (see cms-api/src/app.module.ts).
Start the API
cd cms-api
npm install
npm run start:devThe API comes up at http://localhost:3000/api.
On first run the seed module automatically creates:
- the Administrator role
- the admin user —
admin@cms.local, with a random password printed in the log on the API’s first start (look for theUsuário administrador criadoblock). The password starts expired: the first sign-in requires changing it before any screen opens. - the Tools catalog (menus and permissions)
Start the frontend
cd cms-frontend
npm install
npm run devFrontend at http://localhost:3001. Login: admin@cms.local, with the password shown in the API
log on the first run — it is never displayed again, and must be changed on first sign-in.
Start the documentation (optional)
cd cms-docs
npm install
npm run devDocumentation at http://localhost:3002/docs.
Environment variables
cms-api/.env
# The passwords below come from the root .env, which ./scripts/instalar.sh generates on first run.
# No example value works: the boot check refuses to start with a published secret (see
# cms-api/src/common/validar-segredos-boot.ts), and that includes the one inside DATABASE_URL.
DATABASE_URL=postgres://cms:<POSTGRES_PASSWORD from .env>@localhost:5432/cms
REDIS_URL=redis://:<REDIS_PASSWORD from .env>@localhost:6379
JWT_SECRET=<openssl rand -hex 32>
JWT_EXPIRES_IN=8h
APP_PORT=3000
APP_URL=http://localhost:3000
CORS_ORIGIN=http://localhost:3001
WORKER_CONCURRENCY=100
DB_POOL_MAX=70
HTTP_BACKLOG=1024
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
SMTP_FROM=noreply@cms.localFor SQL Server, replace DATABASE_URL with DB_TYPE=mssql plus MSSQL_HOST, MSSQL_PORT
(default 1433), MSSQL_USER, MSSQL_PASSWORD, MSSQL_DATABASE, MSSQL_ENCRYPT and
MSSQL_TRUST_SERVER_CERTIFICATE.
In development the SAP connectors (cms-sap-connector, cms-idoc-connector) start with the
local stub: you do not need the SAP NW RFC SDK installed to run the rest of the system. To talk
to a real SAP, install the SDK and set SAP_RFC_CLIENT=real — see
SAP RFC / BAPI.
Services and ports
| Service | Port | Notes |
|---|---|---|
cms-api | 3000 | NestJS |
cms-frontend | 3001 | Next.js |
cms-docs | 3002 | Nextra (this documentation) |
cms-sap-connector | 4000 | Published on the host because in dev cms-api runs natively on Windows |
cms-idoc-connector | — | Internal compose network only |
| PostgreSQL | 5432 | |
| Redis | 6379 | |
| Nginx | 80 / 443 | Full compose stack only · HTTP/2 on the app vhost |
Test environment (external systems)
The databases and the broker used to exercise the connectors are not in docker-compose.yml:
they live in a separate Docker project, industry-test, described by docker-compose.test.yml.
From the CMS point of view they are the customer’s systems — they are not part of the stack that
gets installed.
docker compose -f docker-compose.test.yml up -d # everything but SQL Server
docker compose -f docker-compose.test.yml --profile mssql up -d # + SQL Server (~1 GB)
docker compose -f docker-compose.test.yml down # tears down the test environment only| Service | Published port | Address in the External Connection | Access |
|---|---|---|---|
| TimescaleDB (external Postgres) | 5433 | host.docker.internal:5433 | cms / TIMESCALE_PASSWORD, database cms_timeseries |
| InfluxDB | 8086 | http://host.docker.internal:8086 | org cms, token INFLUX_TOKEN |
MongoDB (replica set rs0) | 27017 | mongodb://host.docker.internal:27017/?directConnection=true | cms / MONGO_PASSWORD |
| Mosquitto (MQTT) | 18830 | host.docker.internal:18830 | no authentication |
SQL Server — profile mssql | 1433 | host.docker.internal:1433 | sa / MSSQL_SA_PASSWORD |
| Oracle XE (~2 GB RAM) | 1521 | host.docker.internal:1521, service XEPDB1 | system / ORACLE_PASSWORD |
The port is always the published one (middle column), including where it differs from the
internal one: Mosquitto listens on 1883 inside the Docker network and on 18830 out here,
TimescaleDB on 5432 and 5433. And the host is host.docker.internal, not the service name: it is
the only name cms-api can reach in both execution modes — in a container and running natively on
the machine, without Docker. A connection saved as mongo:27017 only works in the first.
File and SQLite connections have the same mismatch in a different field: they store a path
(/data/arquivos/...) that exists inside the container because the compose mounts
./data/arquivos there. Running natively on Windows, Node resolves /data/arquivos to
<current drive>:\data\arquivos, which does not exist — the symptom is the Application stuck OFF
with “directory does not exist”.
A junction makes the same path valid in both modes, with no change to the stored Connections (adjust the drive to match your clone):
mklink /J X:\data X:\cms\dataNo administrator rights needed. One side effect worth knowing while the source is OFF: the scheduler
SKIPS the Collection without consuming an attempt (see coletarOrigens), so a message of that
Definition already in AGUARDANDO_REENVIO stays put until the Application is back. That is the Keep
Alive gate working, not a stuck queue — but it counts as “Processing” in the monitor meanwhile.
The upper-case names are variables in the root .env, generated by ./scripts/instalar.sh.
Until 2026-09-01 this table published the values and docker-compose.test.yml carried them as
defaults — which looked harmless (back then these containers only published on 127.0.0.1 and never ship to a
customer) until the audit showed how the problem actually travels: a password written in the
repository gets reused, and what starts as a QA default resurfaces in a real installation. That is
why no password appears in this table, and why validar-segredos-boot.ts refuses to start the API
with any known example value.
Changing one of these after the first start does not change the database: Postgres, Mongo and
Influx write the password into the volume on init. Either run the equivalent ALTER USER
inside the container, or throw the volume away — these are disposable:
docker compose -f docker-compose.test.yml down -v.
The containers join the cms-network created by docker-compose.yml — bring the main stack up at
least once first, otherwise Compose complains that the external network does not exist. They also
carry no restart policy: rebooting the machine does not bring them back, run up -d again.
Help Desk tools
The ticket tools (Help Desk) live in a third Docker project,
industry-helpdesk, described by docker-compose.helpdesk.yml. Both simulate customer systems, with
different roles: industry-test holds the data sources the connectors read and write; this one holds
the escalation channels of an alert. Whoever works on a database connector does not need GLPI
running, and tearing down the databases does not take the ticket tool with it.
docker compose -f docker-compose.helpdesk.yml up -d # brings up the Help Desk group
docker compose -f docker-compose.helpdesk.yml down # tears down only this group (volumes stay)| Service | Published port | Base URL on activation | Access |
|---|---|---|---|
| GLPI | 8380 | http://host.docker.internal:8380/apirest.php | glpi / glpi on first access; database with GLPI_DB_PASSWORD |
The same network and address rules as the test environment apply: cms-network created first, and
host.docker.internal with the published port.
GLPI needs three manual steps after the first start, only once (they persist in the volume): turn
on the API with credential login, remove the API client IP restriction, and turn on time zones with
the instance default time zone. The commands are in the header of docker-compose.helpdesk.yml.
Without the third one, the tickets’ opening time shows up ahead — and anyone already signed in has to
sign out and back in to see the right time.
The other tools in the catalogue (ServiceNow, Jira SM, Freshservice, Zendesk, InvGate) are SaaS and have no Docker image. For those, the free accounts do the job: the ServiceNow developer instance (PDI), the Jira SM Free plan and the trials of the others.