Deploying a Web App
When scaling your applications from development to production, Docker provides a consistent, portable environment that eliminates “it works on my machine” headaches. This section walks you through deploying three common web application stacks—Node.js, PHP, and React—on a VPS host. Each example includes production-ready configurations, security best practices, and real-world deployment workflows.
Node.js App
Node.js applications thrive in Docker due to their lightweight, event-driven architecture. We’ll deploy a basic Express.js app with production optimizations.
Step 1: Create a minimal Express app
Start with a simple app that serves a “Hello World” response. This demonstrates core deployment patterns without unnecessary complexity.
<code class="language-bash">mkdir node-app && cd node-app <p>npm init -y</p> <p>npm install express</code>
Step 2: Build a production-ready Dockerfile
This Dockerfile includes Node.js 20, production build flags, and security hardening:
<code class="language-dockerfile"># Use a minimal Node base image with security updates <p>FROM node:20.12.2-alpine</p> <h1>Set working directory</h1> <p>WORKDIR /app</p> <h1>Copy package.json and install dependencies</h1> <p>COPY package.json . </p> <p>RUN npm install --production</p> <h1>Copy application code</h1> <p>COPY . .</p> <h1>Expose port 3000 (default for Express)</h1> <p>EXPOSE 3000</p> <h1>Run the production server</h1> <p>CMD ["node", "server.js"]</code>
Step 3: Deploy to your VPS
On your VPS (with Docker installed), follow these steps:
- Create a
docker-compose.ymlfile:
<code class="language-yaml">version: '3.8'</p> <p>services:</p> <p> web:</p> <p> build: .</p> <p> ports:</p> <p> - "3000:3000"</p> <p> environment:</p> <p> - NODE_ENV=production</p> <p> restart: always</p> <p> healthcheck:</p> <p> test: ["CMD", "curl", "-f", "http://localhost:3000/health"]</p> <p> interval: 30s</p> <p> timeout: 10s</code>
- Run the deployment:
<code class="language-bash">docker-compose up -d</code>
Key production considerations
- Health checks: Prevents Docker from restarting failed containers (critical for production stability)
- Production flags:
NODE_ENV=productiontriggers optimized builds and security hardening - Alpine base: Reduces image size by ~70% vs. Debian-based images
- Port mapping: Exposes port 3000 to your VPS’s public IP for web traffic
💡 Pro tip: Always add a health check to your Dockerfile. This ensures your application is ready to handle traffic before Docker restarts it.
PHP App
PHP applications benefit from Docker’s isolation, especially when using modern PHP versions with Composer and production configuration.
Step 1: Create a PHP application
We’ll use a simple Laravel app for demonstration (though any PHP stack works). Initialize with:
<code class="language-bash">mkdir php-app && cd php-app <p>composer init -y</p> <p>composer require laravel/laravel</code>
Step 2: Build a secure PHP Docker image
This configuration uses PHP 8.2, FPM (for better performance than CLI), and security best practices:
<code class="language-dockerfile"># Use official PHP 8.2 FPM image with security updates <p>FROM php:8.2-fpm</p> <h1>Set working directory</h1> <p>WORKDIR /var/www</p> <h1>Install PHP extensions needed for production</h1> <p>RUN docker-php-extension-installer redis mbstring pdo_mysql</p> <h1>Copy composer.json and install dependencies</h1> <p>COPY composer.json composer.json</p> <p>RUN composer install --no-dev</p> <h1>Copy application code</h1> <p>COPY . .</p> <h1>Configure PHP for production</h1> <p>COPY php.ini /etc/php/8.2/fpm/php.ini</p> <h1>Set environment variables</h1> <p>ENV DISPLAY_NAME="Production"</p> <p>ENV APP_ENV=production</p> <h1>Run as non-root user for security</h1> <p>USER 999:999</p> <h1>Expose port 9000 (FPM default)</h1> <p>EXPOSE 9000</code>
Step 3: Deploy to your VPS
On your VPS:
- Create a
docker-compose.yml:
<code class="language-yaml">version: '3.8'</p> <p>services:</p> <p> web:</p> <p> build: .</p> <p> ports:</p> <p> - "9000:9000"</p> <p> environment:</p> <p> - APP_ENV=production</p> <p> restart: always</p> <p> healthcheck:</p> <p> test: ["CMD", "curl", "-f", "http://localhost:9000/health"]</p> <p> interval: 30s</p> <p> timeout: 10s</code>
- Run the deployment:
<code class="language-bash">docker-compose up -d</code>
Critical security enhancements
| Feature | Why it matters | Example |
|---|---|---|
USER 999:999 |
Prevents privilege escalation | Runs as non-root user |
php.ini |
Configures security settings | Disables unsafe functions |
restart: always |
Ensures service resilience | Auto-restarts on failure |
healthcheck |
Prevents traffic to unhealthy services | 30s interval, 10s timeout |
⚠️ Warning: Always disable
display_errorsin production PHP configs. This prevents sensitive errors from leaking to users.
React Build
React applications are typically static assets after build. We’ll deploy a production build using Docker for consistent delivery.
Step 1: Create a React app
Start with a basic React project:
<code class="language-bash">npx create-react-app react-app <p>cd react-app</p> <p>npm install</code>
Step 2: Build for production
Generate a production-ready static build:
<code class="language-bash">npm run build</code>
Step 3: Dockerize the static build
This Dockerfile serves the React build with Nginx for production performance:
<code class="language-dockerfile"># Use lightweight Nginx base image <p>FROM nginx:alpine</p> <h1>Copy production build</h1> <p>COPY build /usr/share/nginx/html</p> <h1>Configure Nginx for production</h1> <p>COPY nginx.conf /etc/nginx/conf.d/default.conf</p> <h1>Set security headers</h1> <p>RUN echo "add_header X-Content-Type-Options nosniff;" >> /etc/nginx/conf.d/default.conf</p> <p>RUN echo "add_header X-Frame-Options DENY;" >> /etc/nginx/conf.d/default.conf</p> <h1>Expose port 80 (HTTP)</h1> <p>EXPOSE 80</code>
Step 4: Deploy to your VPS
On your VPS:
- Create
nginx.conf:
<code class="language-nginx">server {</p>
<p> listen 80;</p>
<p> server<em>name </em>;</p>
<p> location / {</p>
<p> root /usr/share/nginx/html;</p>
<p> index index.html;</p>
<p> try_files $uri $uri/ /index.html;</p>
<p> }</p>
<p>}</code>
- Run the deployment:
<code class="language-bash">docker-compose up -d</code>
Why this works for React
- Client-side rendering: React builds to static files (no server-side rendering needed)
- Nginx optimization: Handles static assets efficiently with cache control
- Security headers: Prevents common attacks like XSS and clickjacking
- 404 handling:
try_filesensures clean 404 responses for client-side routes
✨ Pro tip: For larger React apps, use Docker’s multi-stage build to reduce image size by ~50% (we’ll skip this for brevity but it’s a key production practice).
Summary
You’ve now deployed three critical web application stacks using Docker on VPS hosting:
- Node.js apps benefit from lightweight containers with health checks and production optimizations
- PHP apps leverage Dockerized FPM with security-hardened configurations and proper environment variables
- React builds work best as static assets served through Nginx with security headers
Each deployment follows identical patterns: Dockerfile configuration, docker-compose orchestration, and health checks for production resilience. The key takeaway is consistency—your development environment becomes identical to production, eliminating configuration drift. Remember to always test deployments in staging before production, and monitor your VPS with tools like docker stats to ensure smooth operation. 🚀