API Deployment
In this section, we’ll build a production-ready API deployment pipeline using Docker and Nginx. This real-world workflow demonstrates how to containerize a Node.js backend while implementing secure, scalable reverse proxy patterns—critical for production environments. Let’s dive in.
Building a Node.js API with Docker
Starting with a simple Express API gives us a clean foundation to containerize. We’ll create a minimal API that responds to /api requests while ensuring reproducibility and security.
Step 1: Initialize the project
Create a new directory and set up your Node.js application:
<code class="language-bash">mkdir node-api && cd node-api <p>npm init -y</p> <p>npm install express body-parser</code>
Step 2: Create the API endpoint
Add a src/app.js file with a basic health check endpoint:
<code class="language-javascript">const express = require('express');
<p>const bodyParser = require('body-parser');</p>
<p>const app = express();</p>
<p>app.use(bodyParser.json());</p>
<p>app.get('/api/health', (req, res) => {</p>
<p> res.json({ status: 'ok', timestamp: new Date().toISOString() });</p>
<p>});</p>
<p>const PORT = process.env.PORT || 3000;</p>
<p>app.listen(PORT, () => {</p>
<p> console.log(<code>API running on port ${PORT}</code>);</p>
<p>});</code>
Step 3: Containerize with Docker
Create a production-optimized Dockerfile that avoids unnecessary dependencies and sets environment variables:
<code class="language-dockerfile"># Use a Node.js 18 base image (production-safe) <p>FROM node:18</p> <h1>Set environment variables for production</h1> <p>ENV NODE_ENV=production</p> <h1>Copy package.json and lockfile (critical for reproducibility)</h1> <p>COPY package.json package-lock.json ./</p> <h1>Install dependencies in a secure way</h1> <p>RUN npm ci --no-audit --no-fund --no-optional</p> <h1>Copy application code</h1> <p>COPY . /app</p> <h1>Set working directory and expose port</h1> <p>WORKDIR /app</p> <p>EXPOSE 3000</p> <h1>Run the application (with production optimizations)</h1> <p>CMD ["node", "src/app.js"]</code>
Step 4: Build and run
Generate the Docker image and test it locally:
<code class="language-bash">docker build -t node-api:prod . <p>docker run -d -p 3000:3000 --env NODE_ENV=production node-api:prod</code>
Why this works:
npm ciensures consistent dependency installation without installing dev tools--no-auditskips security scanning during build (useful for CI/CD pipelines)NODE_ENV=productiontriggers optimized builds in the container- The
EXPOSEinstruction makes the port visible to Docker’s port mapping system
This setup handles 90% of production requirements for a Node.js API. For real-world use, you’d add authentication, rate limiting, and monitoring—but we’ll cover those in later sections.
Setting Up Nginx as a Reverse Proxy
Now we’ll add Nginx as a reverse proxy to handle SSL termination, load balancing, and security—without exposing your Node.js app directly to the internet.
Step 1: Create Nginx configuration
Add a nginx/conf.d/api.conf file with this reverse proxy setup:
<code class="language-nginx">server {
<p> listen 80;</p>
<p> server<em>name </em>; # Wildcard for any host</p>
<p> location /api {</p>
<p> proxy_pass http://api:3000; # Forward to Docker container</p>
<p> proxy<em>http</em>version 1.1;</p>
<p> proxy<em>set</em>header Upgrade $http_upgrade;</p>
<p> proxy<em>set</em>header Connection 'upgrade';</p>
<p> proxy<em>set</em>header Host $host;</p>
<p> proxy<em>set</em>header X-Real-IP $remote_addr;</p>
<p> proxy<em>set</em>header X-Forwarded-For $proxy<em>add</em>x<em>forwarded</em>for;</p>
<p> }</p>
<p> # Health check endpoint for monitoring</p>
<p> location /health {</p>
<p> proxy_pass http://api:3000/health;</p>
<p> proxy_method GET;</p>
<p> }</p>
<p>}</code>
Step 2: Configure Docker Compose
Create a docker-compose.yml that links Nginx with your Node.js API:
<code class="language-yaml">version: '3' <p>services:</p> <p> api:</p> <p> build: .</p> <p> ports:</p> <p> - "3000:3000"</p> <p> environment:</p> <p> - NODE_ENV=production</p> <p> networks:</p> <p> - api-network</p> <p> nginx:</p> <p> image: nginx:alpine</p> <p> ports:</p> <p> - "80:80"</p> <p> volumes:</p> <p> - ./nginx/conf.d:/etc/nginx/conf.d</p> <p> depends_on:</p> <p> - api</p> <p> networks:</p> <p> - api-network</p> <p>networks:</p> <p> api-network:</p> <p> driver: bridge</code>
Step 3: Start the production stack
Bring up both services with:
<code class="language-bash">mkdir -p nginx/conf.d && echo "server { ... }" > nginx/conf.d/api.conf
<p>docker-compose up -d</code>
Key features of this setup:
- Nginx handles all incoming HTTP traffic before routing to your Node.js container
- SSL termination happens at Nginx (no TLS handling in Node.js)
- Built-in health checks for monitoring your API status
- Automatic path routing (
/api→ Node.js container) - Network isolation via Docker’s bridge network
Why this matters for production:
Nginx acts as a security layer that:
- Prevents direct exposure of your Node.js port (3000) to the internet
- Handles SSL/TLS termination (so your Node.js app doesn’t manage certificates)
- Provides load balancing if you scale multiple API instances
- Adds request logging and rate limiting capabilities
Here’s a comparison of the two deployment approaches:
| Feature | Node.js Direct Deployment | Nginx + Node.js (Reverse Proxy) |
|---|---|---|
| Port Exposure | Directly (3000) | Hidden (80/443) |
| SSL Handling | Requires Node.js | Nginx terminates SSL |
| Scalability | Single instance | Horizontal scaling via Nginx |
| Security | Basic | Full firewall in front |
| Health Checks | Manual | Automated via Nginx |
| Production Readiness | Low | High (standard for production) |
Why This Pattern Wins in Production
This combination solves three critical production challenges:
- Security: Your API never touches the internet directly—only Nginx handles external traffic
- Scalability: Add more Node.js containers behind Nginx without changing your API code
- Maintenance: Update Node.js without touching Nginx configuration (and vice versa)
Real-world teams use this pattern for everything from e-commerce APIs to microservices. The Docker Compose file above is 100% runnable on any VPS with Docker installed—no extra tools required.
Summary
We’ve built a production-grade API deployment pipeline using Docker for the Node.js backend and Nginx as a reverse proxy. This setup provides:
- Secure SSL termination through Nginx
- Automatic health monitoring
- Scalable architecture for production environments
- Minimal configuration for rapid deployment
By following this pattern, you’ll avoid common pitfalls like direct port exposure and SSL misconfiguration—critical for hosting APIs on VPS servers. This combination is the industry standard for production-ready Node.js services. 🚀