How we reduced new developer onboarding from 3 days to 15 minutes and eliminated configuration drift across 12 microservices.
The Problem That Made Us Snap
Picture this: A new senior developer joins your team. They’re excited, ready to ship code on day one. Then reality hits.
Day 1: They spend 6 hours installing Node, Docker, configuring ESLint to match the team’s style, figuring out which environment variables go where, and asking Slack questions like, “Which version of NestJS are we on again?”
Day 2: They finally get user-service running, but auth-service throws a CORS error because their local config is slightly different from everyone else’s. They spend the afternoon comparing .env files with a teammate.
Day 3: They submit their first PR. The CI pipeline fails because their local Prettier config formats code differently from the shared config that was updated last month — but nobody told them.
Sound familiar?
We had 12 microservices, a team that was growing fast, and a configuration mess that was eating our productivity alive. Every service had slightly different ESLint rules, Docker setups, folder structures, and VS Code settings. Senior developers were spending hours every week answering setup questions instead of building features.
So we built a system that fixed all of it. Here’s exactly how.
The Solution: One Repo to Rule Them All
We created a central configuration repository called dev-environment-config that acts as the single source of truth for every development environment across our entire microservices ecosystem.
The idea is simple:
One repository controls the setup, structure, linting, Docker config, VS Code settings, and shared code for every service. When a senior developer updates it, every developer’s machine automatically syncs the next morning.
The Architecture at a Glance
dev-environment-config (Central Repo)
├── templates/ → Project scaffolding (NestJS, React)
├── shared/ → ESLint, Prettier, Git configs
├── vscode/ → Extensions & settings
├── docker/ → Base Docker configs
├── scripts/ → Automation (setup, sync, update)
├── common-lib/ → Shared NPM package
└── manifest.json → Version tracking
│ syncs to
▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ user-svc │ │ auth-svc │ │ pay-svc │ ... 12 services
│ .eslint │ │ .eslint │ │ .eslint │
│ .vscode │ │ .vscode │ │ .vscode │
│ Docker │ │ Docker │ │ Docker │
└──────────┘ └──────────┘ └──────────┘
Implementation: Step by Step
Step 1: The Central Config Repository
We created a new repository with this structure:
dev-environment-config/
├── templates/
│ ├── nestjs-service/ # Complete NestJS starter
│ └── reactjs-service/ # Complete React starter
├── shared/
│ ├── .eslintrc.js
│ ├── .prettierrc
│ ├── .editorconfig
│ ├── .gitignore
│ └── commitlint.config.js
├── vscode/
│ ├── extensions.json
│ ├── settings.json
│ └── launch.json
├── scripts/
│ ├── setup-new-developer.sh
│ ├── sync-config.sh
│ ├── auto-update.sh
│ └── create-new-service.sh
├── common-lib/ # Shared NPM package
└── manifest.json # Version tracker
The manifest.json is the heartbeat of the system:
{
"version": "1.2.0",
"lastUpdated": "2024-01-15T10:00:00Z",
"updatedBy": "senior-dev",
"configs": {
"eslint": "3.2.0",
"prettier": "2.1.0",
"docker": "1.5.0"
},
"services": [
"user-service",
"auth-service",
"payment-service"
]
}
Every time a config changes, we bump the version. This is how the auto-update system knows something changed.
Step 2: The One-Command Onboarding Script
This was the game-changer. We wrote a bash script that a new developer runs once, and their entire environment is ready.
The new developer’s experience:
- Install VS Code ✅
- Run one command:
curl -sSL https://raw.githubusercontent.com/company/dev-environment-config/main/scripts/setup-new-developer.sh | bash
- Wait ~10 minutes. Done.
What the script does under the hood:
#!/bin/bash
set -e
WORKSPACE_DIR="$HOME/company-workspace"
CONFIG_REPO="https://github.com/company/dev-environment-config.git"
echo "🚀 Setting up your development environment..."
# 1. Check & install prerequisites
check_and_install "git" "Git"
check_and_install "node" "Node.js" "nvm install 20"
check_and_install "docker" "Docker"
# 2. Create workspace & clone everything
mkdir -p "$WORKSPACE_DIR" && cd "$WORKSPACE_DIR"
git clone "$CONFIG_REPO"
# 3. Clone all service repos from manifest
SERVICES=$(cat dev-environment-config/manifest.json | \
python3 -c "import sys,json; \
[print(s) for s in json.load(sys.stdin)['services']]")
for service in $SERVICES; do
git clone "https://github.com/company/${service}.git"
done
# 4. Install VS Code extensions
EXTENSIONS=$(cat dev-environment-config/vscode/extensions.json | \
python3 -c "import sys,json; \
[print(e) for e in json.load(sys.stdin)['recommendations']]")
for ext in $EXTENSIONS; do
code --install-extension "$ext" --force
done
# 5. Sync shared configs to all services
bash dev-environment-config/scripts/sync-config.sh
# 6. Install dependencies everywhere
for service in $SERVICES; do
cd "$service" && npm install && cp .env.example .env && cd ..
done
# 7. Set up daily auto-update cron (8 AM weekdays)
CRON="0 8 * * 1-5 cd $WORKSPACE_DIR && \
bash dev-environment-config/scripts/auto-update.sh"
(crontab -l 2>/dev/null; echo "$CRON") | crontab -
echo "✅ Setup complete! Open VS Code: code $WORKSPACE_DIR"
That’s it. The developer opens VS Code, and everything is configured — extensions, linting, formatting, Docker, environment variables, the works.
Step 3: The Config Sync Script
This script copies the shared configs from the central repo into every service:
#!/bin/bash
# sync-config.sh
CONFIG_ROOT="$(dirname "$SCRIPT_DIR")"
WORKSPACE_DIR="$(dirname "$CONFIG_ROOT")"
for service in $SERVICES; do
SERVICE_DIR="$WORKSPACE_DIR/$service"
# Copy shared configs
cp -f "$CONFIG_ROOT/shared/.eslintrc.js" "$SERVICE_DIR/"
cp -f "$CONFIG_ROOT/shared/.prettierrc" "$SERVICE_DIR/"
cp -f "$CONFIG_ROOT/shared/.editorconfig" "$SERVICE_DIR/"
cp -f "$CONFIG_ROOT/shared/.gitignore" "$SERVICE_DIR/"
# Copy VS Code settings
mkdir -p "$SERVICE_DIR/.vscode"
cp -f "$CONFIG_ROOT/vscode/settings.json" "$SERVICE_DIR/.vscode/"
cp -f "$CONFIG_ROOT/vscode/extensions.json" "$SERVICE_DIR/.vscode/"
echo "✅ $service synced"
done
Step 4: The Daily Auto-Update (The Magic)
This is the part that keeps everything in sync without anyone thinking about it.
Every morning at 8 AM (weekdays), a cron job runs auto-update.sh on every developer’s machine:
#!/bin/bash
# auto-update.sh — Runs daily at 8 AM
cd "$CONFIG_ROOT"
# 1. Check for config updates
OLD_VERSION=$(cat manifest.json | jq -r '.version')
git pull origin main
NEW_VERSION=$(cat manifest.json | jq -r '.version')
if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
echo "🆕 Config updated: $OLD_VERSION → $NEW_VERSION"
bash "$SCRIPT_DIR/sync-config.sh"
fi
# 2. Pull latest code for all services
for service in $SERVICES; do
cd "$WORKSPACE_DIR/$service"
# Stash local changes safely
git stash push -m "auto-update-$(date +%Y%m%d)" 2>/dev/null
# Pull latest
git pull origin $(git branch --show-current) --rebase
# Update dependencies if package.json changed
if git diff HEAD~1 --name-only | grep -q "package.json"; then
npm install
fi
# Update shared library
npm update @company/common-lib
done
# 3. Notify the developer
notify-send "Dev Environment" "✅ Updated to config v$NEW_VERSION"
The flow:
Senior Dev pushes config change
│
▼
GitHub Actions
│
┌────┴────┐
│ Slack │ Notify team
│ Publish │ common-lib to NPM
└────┬────┘
│
Next 8 AM
│
▼
Every developer's cron runs
│
┌────┴────────────────┐
│ Pull config repo │
│ Sync shared configs │
│ Pull service repos │
│ Update dependencies │
│ Desktop notification│
└─────────────────────┘
Step 5: The Shared Common Library
Instead of copying and pasting guards, interceptors, and DTOs across 12 services, we created @company/common-lib — a private NPM package:
// In any service, just import and use:
import {
AuthGuard,
RolesGuard,
ResponseInterceptor,
LoggingInterceptor,
HttpExceptionFilter,
PaginationDto,
McpService, // Qwen MCP integration
} from '@company/common-lib';
@Controller('users')
@UseGuards(AuthGuard, RolesGuard)
@UseInterceptors(ResponseInterceptor)
@UseFilters(HttpExceptionFilter)
export class UserController {
constructor(private mcpService: McpService) {}
@Get()
findAll(@Query() pagination: PaginationDto) {
return this.userService.findAll(pagination);
}
@Post('analyze')
async analyze(@Body() dto: AnalyzeDto) {
return this.mcpService.callTool('text-analysis', {
text: dto.text,
});
}
}
When we update the common library (add a new guard, fix a bug in the exception filter), the auto-update system bumps the version in every service automatically.
Step 6: Service Scaffolding
Creating a new microservice used to take a day of copy-pasting and tweaking. Now it takes 30 seconds:
bash scripts/create-new-service.sh
# Prompts:
# Service name: order-service
# Description: Handles order processing
# Port: 3002
# Type: nestjs
# ✅ Done! Complete service with:
# - src/common/ (filters, guards, interceptors, DTOs)
# - Docker setup (Dockerfile + docker-compose)
# - VS Code settings
# - ESLint + Prettier
# - Health check endpoint
# - Swagger docs
# - MCP integration
# - Auto-update scripts
The Benefits (With Real Numbers)
1. Onboarding: 3 Days → 15 Minutes
- Before: Manual setup following a 40-step Confluence page.
- After: One
curlcommand. - Before: 3 days to first productive commit.
- After: 15 minutes to running all services.
- Before: "Hey, can you help me with my env vars?" (daily Slack noise).
- After: Zero setup questions.
2. Configuration Drift: Eliminated
Before, every service slowly diverged. user-service had ESLint v8, auth-service had v7. Prettier configs had subtle differences. Docker setups were copy-pasted and never updated. Now, every service has identical configs, and they stay identical because the sync script overwrites them daily.
3. Senior Developer Time: Saved ~5 Hours/Week
Senior devs were spending roughly an hour a day helping juniors with setup issues, reviewing PRs that failed CI due to formatting, and manually updating configs across services. That time is now spent building features.
4. Consistent Code Quality
Every developer has:
- The same ESLint rules (auto-fixed on save)
- The same Prettier config (format on save)
- The same commit message format (enforced by Husky)
- The same VS Code extensions (auto-installed)
PR reviews went from "please fix your formatting" to "let’s discuss the architecture."
5. Zero-Effort Config Updates
When a senior developer wants to:
- 1. Add a new ESLint rule → Push to config repo → Everyone gets it tomorrow
- 2. Update Docker base image → Push → Auto-synced
- 3. Add a VS Code extension → Push → Auto-installed
- 4. Fix a bug in the exception filter → Publish common-lib → Auto-updated No Slack announcements needed. No "please run npm install" messages. It just happens.
6. Standardized Project Structure
Every service looks the same. A developer can jump from user-service to payment-service and immediately know where everything is. The src/common/ folder has the same patterns. The Docker setup is identical. The health check endpoint is at the same path.
What We Learned
Start Small
We didn’t build this all at once. We started with just syncing ESLint and Prettier configs. Once the team saw the value, we added Docker, VS Code settings, the common library, and the auto-update system incrementally.
Don’t Force Everything
We allow service-specific overrides. If payment-service needs a special ESLint rule, they can add a .eslintrc.local.js that extends the shared one. The shared config is the baseline, not a straitjacket.
Notifications Matter
The desktop notification ("✅ Updated to config v1.2.0") was a small touch, but it made developers aware of the system. Before that, updates happened silently and people were confused when things changed.
Version Everything
The manifest.json version tracking was critical. Without it, the auto-update script had no way to know if anything changed, and it would waste time syncing configs that were already up to date.
The Complete Stack
- Backend: NestJS (TypeScript)
- Frontend: ReactJS (TypeScript)
- AI: Qwen MCP
- Containers: Docker, Docker Compose
- Runtime: Node.js 20 LTS
- Database: PostgreSQL 16
- Cache: Redis 7
- CI/CD: GitHub Actions
- Editor: VS Code
- Config Sync: Bash scripts + Cron
TL;DR
If your team has more than 3 microservices and more than 5 developers, you need a centralized dev environment config. Here’s the recipe:
- Create a config repo with templates, shared configs, and scripts.
- Write a setup script that onboards new devs in one command.
- Write a sync script that copies shared configs to all services.
- Set up a cron job that auto-updates every morning.
- Create a shared NPM library for reusable code.
- Build a service generator for scaffolding new microservices.
The result: developers spend their time writing code, not configuring environments.
Have you solved this problem differently at your company? I’d love to hear about it in the comments.
If you found this useful, give it a clap 👏 and follow for more microservices architecture deep-dives.

Top comments (0)