Docker
Multi-stage Dockerfile, compose for local, and image publishing.
Express + TypeScript services ship as a single Node.js container. Socket.io runs on the same HTTP server — no separate worker process.
Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 appuser
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER appuser
EXPOSE 4000
HEALTHCHECK CMD wget -qO- http://localhost:4000/health || exit 1
CMD ["node", "dist/server.js"]Ensure tsconfig.json outputs to dist/ and your entry is src/server.ts (includes both Express and Socket.io).
Build and run
docker build -t backend-api:local .
docker run -p 4000:4000 --env-file .env.local backend-api:local
curl http://localhost:4000/healthPublish to ECR
Images are pushed to Amazon ECR before ECS deployment:
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $ECR_REPO
docker tag backend-api:local $ECR_REPO:$TAG
docker push $ECR_REPO:$TAGSee AWS ECS for task definitions and service updates.
docker-compose (local full stack)
services:
api:
build: .
ports: ["4000:4000"]
env_file: .env.local
depends_on: [postgres]
environment:
DATABASE_URL: postgres://app:app@postgres:5432/app
CORS_ORIGIN: http://localhost:3000
postgres:
image: postgres:16-alpine
ports: ["5432:5432"]
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
volumes: [postgres_data:/var/lib/postgresql/data]
# Optional — only if the service uses MongoDB
mongo:
image: mongo:7
ports: ["27017:27017"]
volumes: [mongo_data:/data/db]
volumes:
postgres_data:
mongo_data:Remove the mongo service if your service uses PostgreSQL only.
Health endpoint
// src/routes/health.routes.ts
router.get("/health", (_req, res) => {
res.json({ status: "ok", uptime: process.uptime() });
});