Local Development Environments
Problemβ
Development teams need consistent, reproducible local environments that match production. Manual setup leads to "works on my machine" issues, configuration drift, and wasted time onboarding new developers.
Prerequisitesβ
- Xec CLI installed (
npm install -g @xec-sh/cli
) - Docker installed (for container-based environments)
- Basic understanding of Xec configuration
Solutionβ
Step 1: Initialize Your Development Environmentβ
Create a .xec/config.yaml
file to define your development environment:
name: my-app-dev
description: Local development environment
targets:
local:
type: local
env:
NODE_ENV: development
DEBUG: "app:*"
containers:
database:
type: docker
image: postgres:15
ports:
- "5432:5432"
env:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: myapp
volumes:
- ./data/postgres:/var/lib/postgresql/data
redis:
type: docker
image: redis:7-alpine
ports:
- "6379:6379"
tasks:
setup:
description: Setup development environment
steps:
- name: Install dependencies
command: npm install
- name: Start services
task: start-services
- name: Run migrations
command: npm run migrate
- name: Seed database
command: npm run seed
start-services:
description: Start all development services
steps:
- name: Start database
command: docker start myapp-postgres || docker run -d --name myapp-postgres -p 5432:5432 -e POSTGRES_USER=dev -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=myapp postgres:15
- name: Start Redis
command: docker start myapp-redis || docker run -d --name myapp-redis -p 6379:6379 redis:7-alpine
- name: Wait for services
command: |
echo "Waiting for services to be ready..."
while ! nc -z localhost 5432; do sleep 1; done
while ! nc -z localhost 6379; do sleep 1; done
echo "All services ready!"
dev:
description: Start development server
steps:
- name: Ensure services running
task: start-services
- name: Start dev server
command: npm run dev
interactive: true
Step 2: Create Environment-Specific Scriptsβ
Create development scripts in .xec/scripts/dev-setup.js
:
#!/usr/bin/env xec
import { $ } from '@xec-sh/core';
// Check required tools
async function checkRequirements() {
const checks = [
{ cmd: 'node --version', name: 'Node.js', min: '18.0.0' },
{ cmd: 'npm --version', name: 'npm', min: '8.0.0' },
{ cmd: 'docker --version', name: 'Docker' }
];
for (const check of checks) {
try {
const result = await $`${check.cmd}`;
console.log(`β ${check.name}: ${result.stdout.trim()}`);
} catch (error) {
console.error(`β ${check.name} not found`);
process.exit(1);
}
}
}
// Setup environment variables
async function setupEnv() {
const envFile = '.env.local';
if (!await $.exists(envFile)) {
console.log('Creating .env.local from template...');
await $`cp .env.example ${envFile}`;
// Generate secrets
const secret = await $`openssl rand -hex 32`;
await $`sed -i '' 's/JWT_SECRET=.*/JWT_SECRET=${secret.stdout.trim()}/' ${envFile}`;
}
}
// Main setup
async function main() {
console.log('π Setting up development environment...\n');
await checkRequirements();
await setupEnv();
// Run setup task
await $`xec run setup`;
console.log('\nβ
Development environment ready!');
console.log('Run "xec run dev" to start the development server');
}
main().catch(console.error);
Step 3: Manage Multiple Environmentsβ
Create profiles for different development scenarios:
# .xec/config.yaml
profiles:
default:
env:
NODE_ENV: development
API_URL: http://localhost:3000
testing:
env:
NODE_ENV: test
DATABASE_URL: postgres://test:test@localhost:5432/test
staging:
env:
NODE_ENV: staging
API_URL: https://staging.example.com
commands:
env:
switch:
description: Switch environment profile
options:
- name: profile
type: string
required: true
choices: [default, testing, staging]
action: |
echo "Switching to ${profile} environment..."
xec config set profile ${profile}
xec run setup
Step 4: Container-Based Developmentβ
For fully containerized development:
# docker-compose.xec.yaml
version: '3.8'
services:
app:
build: .
volumes:
- .:/app
- /app/node_modules
ports:
- "3000:3000"
environment:
- NODE_ENV=development
command: npm run dev
depends_on:
- postgres
- redis
postgres:
image: postgres:15
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
postgres_data:
Use with Xec:
// .xec/scripts/docker-dev.js
#!/usr/bin/env xec
import { $ } from '@xec-sh/core';
const compose = $.docker.compose({
file: 'docker-compose.xec.yaml',
project: 'myapp-dev'
});
// Start development environment
await compose`up -d`;
// Follow logs
await compose`logs -f app`;
Step 5: Hot Reload and File Watchingβ
Set up automatic reload on file changes:
// .xec/scripts/watch.js
#!/usr/bin/env xec
import { $ } from '@xec-sh/core';
import { watch } from 'fs/promises';
const watchers = [
{
path: './src',
pattern: '**/*.{js,ts,jsx,tsx}',
command: 'npm run lint',
debounce: 1000
},
{
path: './tests',
pattern: '**/*.test.{js,ts}',
command: 'npm test',
debounce: 2000
}
];
for (const watcher of watchers) {
$.watch(watcher.path, watcher.pattern, async (event, filename) => {
console.log(`File changed: ${filename}`);
await $`${watcher.command}`.nothrow();
}, { debounce: watcher.debounce });
}
console.log('Watching for changes... Press Ctrl+C to stop');
Best Practicesβ
-
Version Control Your Environment
- Always commit
.xec/config.yaml
- Use
.env.example
templates - Document environment-specific requirements
- Always commit
-
Isolate Dependencies
- Use containers for services
- Avoid global installations
- Pin versions explicitly
-
Automate Everything
- One-command setup
- Automated health checks
- Self-documenting scripts
-
Environment Parity
- Match production versions
- Use same configuration structure
- Test with production-like data
-
Fast Feedback Loops
- Hot reload for code changes
- Automatic test runs
- Instant error notifications
Common Pitfallsβ
-
Hardcoded Paths
- β
/Users/john/projects/myapp
- β
Use relative paths or
${XEC_PROJECT_ROOT}
- β
-
Missing Health Checks
- β Assuming services are ready immediately
- β Always wait for services to be healthy
-
Uncommitted Environment Files
- β Forgetting to update
.env.example
- β Use git hooks to check for updates
- β Forgetting to update
-
Resource Conflicts
- β Fixed ports without checking availability
- β Use dynamic ports or check before binding
Troubleshootingβ
Issue: Port Already in Useβ
# Find process using port
lsof -i :3000
# Kill process or use different port
xec config set ports.app 3001
Issue: Container Permissionsβ
# Fix volume permissions
services:
app:
user: "${UID}:${GID}"
volumes:
- .:/app:delegated
Issue: Slow File Syncingβ
# Optimize Docker volume performance
volumes:
- .:/app:cached # For read-heavy workloads
- ./src:/app/src:delegated # For write-heavy workloads
Issue: Environment Variables Not Loadingβ
// Check environment loading
await $`xec run env:check`;
// Debug specific variable
console.log(process.env.MY_VAR);
Related Guidesβ
- Debugging - Debugging Xec scripts
- Container Orchestration - Advanced Docker workflows