Deploying a Node.js / Express App with Neo
A minimal Dockerfile and .neo.yml for a Node API — port detection, env vars, and a PM2-free process model.
A minimal Dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Neo reads the EXPOSE 3000 line to auto-detect the port when nothing else specifies it — no config needed for a first deploy.
No process manager required
Skip PM2, forever, or nodemon inside the container. Docker's own restart policy (restart: unless-stopped, Neo's default) handles crash recovery, and Neo's blue-green swap handles zero-downtime deploys at the container level — a process manager inside the container is solving a problem Neo already solves outside it.
.neo.yml
name: api
port: 3000
env:
NODE_ENV: production
env_file: .env.production
neo deploy --domain api.example.com
Background jobs
If the app has a separate worker process (a BullMQ consumer, a cron-like loop), give it its own entry rather than running it inside the same container as the HTTP server:
workers:
jobs:
command: node worker.js
This runs as its own container sharing the app's image and environment — so a crash in the worker never takes the API down with it, and vice versa.
Multi-stage builds for smaller images
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
Neo builds whichever stage is last in the file by default — nothing special to configure for a multi-stage Dockerfile.
Try it yourself — Neo is free, with every feature unlocked.
Get started free