Skip to main content

Deployment & Infrastructure

All Xylem services run on a single AWS EC2 instance, managed by PM2, and served through Nginx with Let's Encrypt SSL.

PM2 Process Management

All services are defined in the root ecosystem.config.js:

ecosystem.config.js
module.exports = {
apps: [
{
name: "xylem-frontend",
cwd: "./xylem-frontend",
script: "npm",
args: "run start",
env: { PORT: 4801 },
},
{
name: "xylem-backend",
cwd: "./xylem-backend",
script: "node",
args: "dist/src/main",
},
{
name: "xylem-sme-analyzer",
cwd: "./xylem-sme-analyzer",
script: "uv",
args: "run uvicorn app.main:app --host 0.0.0.0 --port 4802 --timeout-keep-alive 300",
},
{
name: "xylem-simulation-backend",
cwd: "./xylem-simulation-backend",
script: "uv",
args: "run uvicorn src.api.main:app --host 0.0.0.0 --port 4902 --timeout-keep-alive 300",
},
{
name: "xylem-geo",
cwd: "./xylem-geo",
script: "node",
args: "dist/server.js",
env: { PORT: 4830, BASE_URL: "https://geo.xylem.city" },
},
],
};

Common PM2 Commands

# Start all services
pm2 start ecosystem.config.js

# Restart a specific service
pm2 restart xylem-backend

# View logs
pm2 logs xylem-backend --lines 100

# Monitor all processes
pm2 monit

# Save current process list (survives reboot)
pm2 save

# Set up PM2 to start on boot
pm2 startup

Nginx Configuration

Each service has its own Nginx server block in /etc/nginx/sites-available/.

Domain Mapping

DomainTypeTarget
xylem.cityReverse proxylocalhost:4801 (Next.js)
api.xylem.cityReverse proxylocalhost:4000 (NestJS)
admin.xylem.cityStatic files/var/www/xylem/xylem-admin/dist
geo.xylem.cityReverse proxylocalhost:4830 (Express)
sme.xylem.cityReverse proxylocalhost:4802 (FastAPI)
internal.docs.xylem.cityStatic files/var/www/xylem/xylem-internal-docs/build

Example: Static Site Config (Admin)

/etc/nginx/sites-available/admin.xylem.city
server {
server_name admin.xylem.city;

root /var/www/xylem/xylem-admin/dist;
index index.html;

location / {
try_files $uri $uri/ /index.html;
}

listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/admin.xylem.city/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/admin.xylem.city/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

server {
listen 80;
server_name admin.xylem.city;
return 301 https://$host$request_uri;
}

SSL Certificates

SSL is managed by Certbot (Let's Encrypt). Certificates auto-renew via a systemd timer.

# Issue a new certificate
sudo certbot --nginx -d internal.docs.xylem.city

# Check renewal status
sudo certbot certificates

# Test auto-renewal
sudo certbot renew --dry-run

Directory Layout

/var/www/xylem/
├── ecosystem.config.js # PM2 process definitions
├── xylem-backend/ # NestJS API
│ ├── src/ # Source code
│ ├── prisma/ # Database schema + migrations
│ ├── assets/ # Static GIS files (GeoTIFF, GeoJSON)
│ ├── uploads/ # User file uploads
│ └── dist/ # Compiled output
├── xylem-frontend/ # Next.js app
│ ├── app/ # App Router pages + components
│ └── public/ # Static assets
├── xylem-admin/ # Vite SPA
│ ├── src/ # React source
│ └── dist/ # Production build (served by Nginx)
├── xylem-geo/ # Tile server
│ ├── src/ # TypeScript source
│ ├── raster-tiles/ # Raster tile files
│ ├── vector-tiles/ # Vector tile files (.pmtiles)
│ └── layers.db # SQLite layer metadata
├── xylem-simulation-backend/ # Python simulation
│ └── src/ # Python source
├── xylem-sme-analyzer/ # Python DXF→PDF
│ └── app/ # FastAPI app
└── xylem-internal-docs/ # This documentation site
├── docs/ # Markdown content
└── build/ # Production build

Build & Deploy Workflow

Backend

cd xylem-backend
npm run build # Compiles TypeScript → dist/
pm2 restart xylem-backend

Frontend

cd xylem-frontend
npm run build # Next.js production build
pm2 restart xylem-frontend

Admin Panel

cd xylem-admin
npm run build # Vite build → dist/
# No PM2 restart needed — Nginx serves static files

Geo Server

cd xylem-geo
npx tsc # Compile TypeScript
pm2 restart xylem-geo

Documentation Site

cd xylem-internal-docs
npm run build # Docusaurus build → build/
# No PM2 restart needed — Nginx serves static files