NestJS monorepo template with a pre-configured DevOps foundation.
Most NestJS projects start from a nest new and then incrementally add Docker Compose, CI/CD, secrets management, and deployment automation - each requiring separate setup and maintenance. This repository provides that foundation already assembled: environment separation via Docker Compose profiles, a GitHub Actions pipeline with incremental builds and image scanning, Ansible playbooks for provisioning and deployment to a VPS, and a NestJS monorepo structure with tooling configured.
What is pre-configured out of the box:
- Four environments via Docker Compose profiles (local-dev / shared-dev / stage / prod)
- Incremental CI/CD via
dorny/paths-filter- only changed services rebuild; Trivy scans every image - Ansible IaC:
generate-vault.pyconverts GitHub Secrets to Ansible Vault, playbooks provision and deploy to VPS - NestJS monorepo with
pnpmworkspaces, SWC, strict ESLint/Prettier, Husky, commitlint,@nestjs/terminus,prom-client
- Quick Start
- Environment Strategy
- What Is Pre-configured
- Repository Structure
- Configuration Management
- Common Commands
- Documentation
- Technology Stack
- Testing
- Adding a New Microservice
- Troubleshooting
Prerequisites:
- Docker & Docker Compose v2+
- Node.js 22+
- pnpm 11+
- Make
First time setup:
# 1. Initialize configuration files from examples
make init
# 2. Edit the generated .env.infra
# Set ENVIRONMENT=local-dev
# Set passwords for databases
# 3. Install dependencies and register git hooks
pnpm install
pnpm prepare
# 4. Start infrastructure
make upmake check-services
make logs| Environment | Active Services | Use Case |
|---|---|---|
local-dev |
Postgres, MongoDB, Redis, MinIO | Developer laptop |
shared-dev |
+ Keycloak, Redpanda, Nginx | Shared VDS; local devs connect to it remotely |
stage |
Full stack, no MinIO (uses cloud S3) | Pre-production testing |
prod |
Full stack + Prometheus/Grafana | Production |
ENVIRONMENT is read from .env.infra. On local-dev, Keycloak and Redpanda are not started - they run on the shared VDS and local services connect via the external port. This means a developer laptop only runs lightweight infra.
The Docker Compose profiles that make up activates are not 1:1 with environment names. The prod environment, for example, activates both the prod and monitoring profiles. See Architecture Overview for the exact mapping.
Infrastructure & DevOps:
- Docker Compose multi-environment orchestration with profiles
- Ansible deployment automation for
shared-dev,stage,prod - GitHub Actions CI/CD: path-based incremental builds, Trivy vulnerability scanning,
ansible-lint - Secrets: Ansible Vault + GitHub Secrets pipeline
- SSL/TLS: Let's Encrypt with auto-renewal via Certbot
- Monitoring: Prometheus + Grafana (prod only)
NestJS application layer:
- Node.js v22, NestJS v11, TypeScript with strict mode
swccompiler for builds and Jest transformspnpmworkspaces:apps/*for services,libs/*for shared code- ESLint (flat config), Prettier,
lint-staged,huskypre-commit hooks commitlintwith Conventional Commits configpinofor JSON logging,@nestjs/terminusfor healthchecks,prom-clientfor Prometheus metrics
.
├── apps/
│ └── user-service/
├── libs/
├── infrastructure/
│ ├── ansible/
│ └── keycloak/
├── compose/
│ ├── infra.yml
│ └── monitoring.yml
├── config/
│ ├── postgres/
│ ├── mongodb/
│ ├── redpanda/
│ ├── nginx/
│ └── minio/
├── scripts/
├── makefiles/
└── docs/
apps/- NestJS microservices (one per subdirectory, each is an independent pnpm workspace package)libs/- Shared TypeScript libraries consumed by servicesinfrastructure/- Ansible playbooks, roles, Keycloak realm customizationcompose/- Docker Compose files for infra services and monitoringconfig/- Init scripts and templates for databases and infrastructure servicesscripts/- Health check, backup, SSL utility scriptsmakefiles/- Modular Makefile includes split by concerndocs/- Architecture docs and ADRs
Initialization scripts and configuration templates for infrastructure services. Handles environment-aware service initialization and secrets injection at container startup.
Structure:
config/
├── postgres/
│ ├── init-db.sh
│ ├── check-and-init.sh
│ ├── init-users.conf (gitignored, generated from .example)
│ └── init-users.conf.example
├── mongodb/
├── redpanda/
├── nginx/
└── minio/
Example - config/postgres/init-users.conf:
user_service:USER_SERVICE_DB_PASSWORD:users
Format: username:ENV_VAR_NAME:database. When the container starts, init-db.sh reads this file, resolves $USER_SERVICE_DB_PASSWORD from the environment, and creates the user and database if they do not exist. Adding a new service database is a one-line change in this file.
The Nginx config works the same way: config/nginx/default.conf.template contains $VARIABLE placeholders that are substituted at container startup via envsubst from values in .env.infra.
Local development:
- Run
make initto copy all.examplefiles - Edit
.env.infrawith database passwords - Edit
config/postgres/init-users.confto map service users to env vars
Production:
GitHub Secrets -> generate-vault.py -> vault.yml -> Ansible -> .env.infra on server
See infrastructure/README.md for the full Ansible flow.
# Start infrastructure for current ENVIRONMENT
make up
# Stop services
make down
# View logs
make logs
# Build all NestJS apps
pnpm build
# Run all workspace tests
pnpm test
# Health check all containers
make check-servicesService-specific:
make up SERVICES=user-service
make restart SERVICES=user-service
make logs SERVICE=user-serviceFor complete command reference, see docs/MAKEFILE.md.
- Architecture Overview - Deployment model, environment strategy, secrets flow
- Makefile Reference - All
makecommands - Infrastructure Guide - Ansible deployment process
- ADRs - Architecture decision records
Backend:
- Node.js 22, NestJS 11, TypeScript (strict)
pnpm11 workspacesswc(compilation and Jest transforms)
Infrastructure:
- PostgreSQL, MongoDB, Redis
- Redpanda (Kafka-compatible, single-binary, no Zookeeper)
- Keycloak (OAuth2/OIDC identity provider)
- MinIO (S3-compatible, local-dev only)
- Nginx (reverse proxy with SSL termination)
DevOps:
- Docker Compose with profiles
- Ansible (deployment automation, see ADR-005)
- GitHub Actions (CI/CD)
- Prometheus + Grafana (prod only)
Docker Compose was chosen over Kubernetes to avoid cluster management overhead at this scale. See ADR-003 for the rationale and defined migration triggers.
# Unit tests across all workspace packages
pnpm test
# E2E tests
pnpm test:e2e
# Ansible role tests (Molecule)
make test-ansible- Generate the app:
nest generate app your-service - Add a service definition to
docker-compose.ymlfollowing theuser-serviceexample - Create
apps/your-service/.env(based on.env.infra.examplepattern) - Add the path to
dorny/paths-filterin the CI workflow so incremental builds detect it - Add database users to
config/postgres/init-users.confif needed
Services won't start:
make check-services
make logs SERVICE=user-serviceDatabase connection issues:
make exec-postgres
# inside psql: \l to list databases, \du to list usersPort conflicts:
make down
make clean
make up