One-command observability stack for Node.js — Elasticsearch · Kibana · APM Server · Docker Compose
Stop spending hours wiring up APM from scratch. This boilerplate gives you a fully configured Express.js application with Elastic APM instrumentation, a production-grade 3-node Elasticsearch cluster, Kibana dashboards, and APM Server — all running locally with a single docker-compose up.
Clone it, run two commands, and you'll have live traces, error tracking, and performance metrics in Kibana within minutes.
- What is this?
- Who is it for?
- What's Included
- Quick Start
- Test Endpoints
- Architecture Overview
- Why a 3-Node Cluster?
- Monitoring Setup
- Configuration
- Security Notes
- Project Structure
- Troubleshooting
- Contributing
This is a ready-to-use starter template that integrates:
- An Express.js 5 application pre-wired with the
elastic-apm-nodeagent - A complete Elastic Stack (Elasticsearch + Kibana + APM Server) running via Docker Compose
- 5 demo endpoints that exercise different APM monitoring scenarios (errors, async ops, CPU load, DB spans)
You get the full observability pipeline — from your Node.js app all the way to Kibana dashboards — with zero manual configuration.
| You are... | This repo helps you... |
|---|---|
| A Node.js developer new to APM | Learn Elastic APM without fighting setup |
| A backend engineer evaluating observability tools | Get a working reference implementation fast |
| A DevOps / Platform engineer | Use as a base for onboarding teams to Elastic observability |
| A student / learner | Understand how distributed tracing and APM work end-to-end |
- Pre-configured Express.js server with
elastic-apm-nodeagent - 5 sample endpoints for testing different monitoring scenarios
- Centralized error handling with automatic APM error capture
- Request logging middleware and randomized latency for realistic traces
- Elasticsearch: 3-node cluster for high-availability data storage
- Kibana: Web UI for APM dashboards and data visualization
- APM Server: Receives and processes performance data from your app
- Setup service: Automates certificate generation and initial passwords
- Docker & Docker Compose installed
- Node.js 16+ (for running the Express app locally)
- 4 GB+ RAM available for the Elasticsearch cluster
git clone https://github.com/Anujsup/elastic-apm-express-boilerplate.git
cd elastic-apm-express-boilerplate
npm install# Copy environment file
cp .env.example .env
# Start Elasticsearch, Kibana, and APM Server
docker-compose up -d
# Wait 2–3 minutes for services to be healthy
docker-compose psnode index.js| Service | URL |
|---|---|
| Express App | http://localhost:3000 |
| Kibana (APM UI) | http://localhost:5601 |
| Elasticsearch | http://localhost:9200 |
| APM Server | http://localhost:8200 |
Auto-Setup: APM integration is pre-configured. No manual setup in Kibana — just start the Express app and your
express-apm-demoservice appears automatically within ~1 minute.
Hit these endpoints to generate APM data across different monitoring scenarios:
| Endpoint | Scenario | What APM captures |
|---|---|---|
GET / |
Basic request | Response time, throughput |
GET /error |
Error generation | Error tracking, stack traces |
GET /async |
Async/Promise flow | Promise handling, async spans |
GET /cpu-intensive |
CPU-bound work | CPU usage, slow transaction detection |
GET /db-simulation |
Database operation | Custom DB span, query latency |
# Quick smoke test — hit all endpoints
curl http://localhost:3000/
curl http://localhost:3000/async
curl http://localhost:3000/cpu-intensive
curl http://localhost:3000/db-simulation
curl http://localhost:3000/error # intentional errorgraph TB
subgraph "External Access"
USER[("User")]
BROWSER[("Browser")]
end
subgraph "Express.js Application"
APP["Express App\nindex.js\nPort: 3000"]
APM_AGENT["APM Agent\nelastic-apm-node"]
end
subgraph "Elastic Stack"
APM_SERVER["APM Server\nPort: 8200"]
subgraph "Elasticsearch Cluster"
ES01["ES01\nMaster Node\nPort: 9200"]
ES02["ES02\nData Node"]
ES03["ES03\nData Node"]
end
KIBANA["Kibana\nPort: 5601"]
SETUP["Setup Service\nCertificates & Passwords"]
end
subgraph "Data Storage"
CERTS[("Certificates\nVolume")]
DATA1[("ES01 Data\nVolume")]
DATA2[("ES02 Data\nVolume")]
DATA3[("ES03 Data\nVolume")]
KIBANA_DATA[("Kibana Data\nVolume")]
end
USER --> APP
BROWSER --> KIBANA
APP --> APM_AGENT
APM_AGENT --> APM_SERVER
APM_SERVER --> ES01
KIBANA --> ES01
SETUP --> ES01
SETUP --> CERTS
ES01 <--> ES02
ES02 <--> ES03
ES03 <--> ES01
ES01 --> DATA1
ES02 --> DATA2
ES03 --> DATA3
KIBANA --> KIBANA_DATA
ES01 --> CERTS
ES02 --> CERTS
ES03 --> CERTS
- Express App instruments every request via the
elastic-apm-nodeagent - APM Agent forwards traces, errors, and metrics to APM Server (port 8200)
- APM Server processes and stores the data in the Elasticsearch cluster
- Kibana reads from Elasticsearch and renders the APM dashboards
- Setup service handles TLS certificate generation on first boot
- Docker volumes persist all data across container restarts
A single Elasticsearch node works fine for basic development. This boilerplate uses 3 nodes to give you a production-realistic environment:
| Benefit | Description |
|---|---|
| High Availability | Cluster keeps running if one node fails |
| Fault Tolerance | Data is replicated across nodes — no data loss |
| Load Distribution | Query and indexing load is spread across nodes |
| Realistic Testing | Experience actual cluster behavior: master election, shard allocation |
- ES01 — Master-eligible node + data node (cluster management, port 9200)
- ES02 — Data node (stores and indexes APM data)
- ES03 — Data node (stores and indexes APM data)
For simple development: Scale down to 1 node by removing
es02andes03fromdocker-compose.ymland settingdiscovery.type: single-nodeon es01.
| Field | Value |
|---|---|
| Username | elastic |
| Password | elastic123 |
- Open http://localhost:5601 and log in
- Navigate to Observability → APM
- On first visit you'll see the welcome screen — that's normal
- Start the Express app and hit a few endpoints
- The
express-apm-demoservice appears automatically within ~1 minute
- Service overview — response times, throughput, error rates
- Transactions — per-endpoint breakdown with latency distributions
- Errors — stack traces with context
- Metrics — CPU, memory, and Node.js runtime metrics
- Traces — distributed trace waterfall (span-level detail)
const apm = require('elastic-apm-node').start({
serviceName: 'express-apm-demo', // appears as service name in Kibana
serverUrl: 'http://localhost:8200',
environment: 'development',
captureBody: 'all' // capture request bodies for debugging
});Copy .env.example to .env before starting the stack. The file contains:
- Elasticsearch credentials and cluster name
- APM Server token configuration
- Kibana connection settings
For POC / development use (current setup)
- HTTP only — no SSL/TLS
- Default passwords (
elastic123) - Authentication enabled but simplified for local use
Before going to production
- Rotate all default passwords
- Enable HTTPS/TLS (certificates are already generated by the setup service)
- Configure network-level access controls
- Use environment-specific
.envfiles and secrets management
elastic-apm-express-boilerplate/
├── index.js # Express app — APM agent + all route handlers
├── docker-compose.yml # Full Elastic Stack (ES cluster + Kibana + APM Server)
├── package.json # Node.js dependencies and npm scripts
├── .env.example # Environment variable template
├── .gitignore
└── README.md
# Check available Docker resources
docker system df
# View logs for a specific service
docker-compose logs -f apm-server
docker-compose logs -f es01
# Full reset (removes all volumes and data)
docker-compose down -v
docker-compose up -d- Confirm APM Server is healthy:
docker-compose ps - Check the Express app console for APM connection errors
- Hit at least one endpoint to generate a transaction:
curl http://localhost:3000/ - Wait ~60 seconds — APM data has a small ingestion delay
- Verify you're on the Observability → APM page, not Metrics or Logs
Increase Docker memory allocation to at least 4 GB in Docker Desktop → Settings → Resources.
Contributions, issues, and feature requests are welcome. Feel free to open an issue or submit a pull request.
Author: Anuj Patil
License: MIT