Deploy Node.js Applications¶
Framework-specific guide for deploying Node.js apps to Airbase
This guide covers Node.js-specific deployment patterns, best practices, and common issues.
Quick Start¶
Complete example available: Node.js Express Example
Basic workflow:
# 1. Create Dockerfile
# 2. Build container
airbase container build
# 3. Deploy
airbase container deploy --yes staging
Time: 5 minutes
Dockerfile Pattern for Node.js¶
Multi-Stage Build (Recommended)¶
Pattern:
# Stage 1: Install dependencies
FROM gdssingapore/airbase:node-22-builder AS builder
WORKDIR /app
COPY --chown=app:app package.json package-lock.json ./
RUN npm ci --only=production
# Stage 2: Runtime
FROM gdssingapore/airbase:node-22
WORKDIR /app
COPY --chown=app:app --from=builder /app/node_modules ./node_modules
COPY --chown=app:app package.json ./
COPY --chown=app:app src ./src
USER app
CMD ["node", "src/index.js"]
Why multi-stage? - ✅ Smaller final image (~200MB vs ~1GB) - ✅ No build tools in production - ✅ Faster deployments - ✅ Better security
Single-Stage Build (Simple)¶
When to use: Very simple apps, rapid prototyping
FROM gdssingapore/airbase:node-22
WORKDIR /app
COPY --chown=app:app package.json package-lock.json ./
RUN npm ci --only=production
COPY --chown=app:app . ./
USER app
CMD ["node", "index.js"]
Required Configuration¶
1. Bind to 0.0.0.0¶
Express:
const PORT = process.env.PORT || 3000;
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server running on port ${PORT}`);
});
Fastify:
Next.js:
2. Use ES Modules (Recommended)¶
package.json:
3. Health Check Endpoint¶
Common Frameworks¶
Express.js¶
See: Node.js Express Example for complete working code
Key points: - Use express.json() middleware - Bind to 0.0.0.0 - Read PORT from environment - Add health check endpoint
Fastify¶
package.json:
index.js:
import Fastify from 'fastify';
const fastify = Fastify({ logger: true });
const PORT = parseInt(process.env.PORT || '3000', 10);
fastify.get('/health', async () => {
return { status: 'ok' };
});
await fastify.listen({ port: PORT, host: '0.0.0.0' });
Next.js¶
Dockerfile:
FROM gdssingapore/airbase:node-22-builder AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . ./
RUN npm run build
FROM gdssingapore/airbase:node-22
WORKDIR /app
COPY --chown=app:app --from=builder /app/.next ./.next
COPY --chown=app:app --from=builder /app/node_modules ./node_modules
COPY --chown=app:app --from=builder /app/package.json ./
COPY --chown=app:app --from=builder /app/public ./public
USER app
CMD ["npm", "start"]
package.json:
Common Issues¶
Issue: "Cannot find module"¶
Cause: Dependencies not installed in Docker image
Solution: - Check COPY --from=builder /app/node_modules line exists - Verify npm ci runs in builder stage - Check .dockerignore doesn't exclude necessary files
Issue: "EADDRINUSE: address already in use"¶
Cause: Port already taken or not binding to correct interface
Solution: - Ensure binding to 0.0.0.0 not localhost - Use process.env.PORT for port number
Issue: "Permission denied"¶
Cause: Files owned by root instead of app user
Solution: - Use --chown=app:app in all COPY commands - Ensure USER app before CMD - Check no RUN chmod commands creating wrong permissions
Issue: Large image size (>500MB)¶
Cause: Not using multi-stage build or including dev dependencies
Solution: - Use multi-stage build pattern - Use npm ci --only=production - Check .dockerignore excludes node_modules, tests, etc.
Best Practices¶
1. Use Lockfiles¶
Always commit: package-lock.json
In Dockerfile: Use npm ci not npm install
2. Minimize Dependencies¶
Check unused packages:
Remove devDependencies from production:
3. Security Scanning¶
Audit dependencies:
4. Environment Variables¶
Read from process.env:
const config = {
port: process.env.PORT || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
databaseUrl: process.env.DATABASE_URL
};
Never hardcode secrets:
5. Graceful Shutdown¶
const server = app.listen(PORT, '0.0.0.0');
process.on('SIGTERM', () => {
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
Performance Optimization¶
Enable Production Mode¶
Set NODE_ENV in Dockerfile:
Or in .env:
Use Clustering (Optional)¶
For CPU-intensive apps:
import cluster from 'cluster';
import os from 'os';
if (cluster.isPrimary) {
const numCPUs = os.cpus().length;
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
// Worker process runs app
app.listen(PORT, '0.0.0.0');
}
Checklist¶
Before deploying:
- Dockerfile uses multi-stage build
- App binds to
0.0.0.0 - PORT read from environment variable
- Health check endpoint implemented
- Using
npm ci --only=production - All files owned by
app:app - USER app before CMD
- .dockerignore excludes unnecessary files
- No hardcoded secrets in code
- package-lock.json committed
See Also¶
- Example: Node.js Express - Complete Express.js example
- How-To: Set Environment Variables - Environment variable configuration
- Reference: Base Images - Node.js base images
- Reference: Dockerfile Requirements - Container requirements