Getting Started with Cloud-Native Development

Build and Containerize Your Application

Step 2 of 3
33%
20 min

Building and Containerizing Applications

Learn how to create containerized applications using Docker best practices. Build a sample web application and create optimized, secure container images ready for production deployment.

Overview

In this step, you’ll create a sample web application and learn how to containerize it using Docker best practices. You’ll build production-ready images that are secure, efficient, and optimized for cloud deployment.

Step 1: Create a Sample Web Application

Let’s create a simple Node.js web application that demonstrates key containerization concepts.

Create the Application Structure

bash
mkdir cloud-native-app && cd cloud-native-app

Create package.json

json
{
  "name": "cloud-native-app",
  "version": "1.0.0",
  "description": "A sample cloud-native web application",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "jest"
  },
  "dependencies": {
    "express": "^4.18.2",
    "helmet": "^7.0.0",
    "cors": "^2.8.5"
  },
  "devDependencies": {
    "nodemon": "^3.0.1",
    "jest": "^29.5.0"
  },
  "keywords": ["docker", "kubernetes", "cloud-native"],
  "author": "Your Name",
  "license": "MIT"
}

Create the Server Application

Create server.js:

javascript
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');

const app = express();
const PORT = process.env.PORT || 3000;

// Security middleware
app.use(helmet());
app.use(cors());
app.use(express.json());

// Health check endpoint
app.get('/health', (req, res) => {
  res.status(200).json({
    status: 'healthy',
    timestamp: new Date().toISOString(),
    version: process.env.APP_VERSION || '1.0.0'
  });
});

// Main application endpoint
app.get('/', (req, res) => {
  res.json({
    message: 'Welcome to Cloud-Native Development!',
    environment: process.env.NODE_ENV || 'development',
    container: process.env.HOSTNAME || 'localhost'
  });
});

// API endpoint
app.get('/api/status', (req, res) => {
  res.json({
    api: 'running',
    uptime: process.uptime(),
    memory: process.memoryUsage(),
    cpu: process.cpuUsage()
  });
});

// Graceful shutdown
process.on('SIGTERM', () => {
  console.log('SIGTERM received, shutting down gracefully');
  process.exit(0);
});

app.listen(PORT, '0.0.0.0', () => {
  console.log(`Server running on port ${PORT}`);
  console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
});

Create Tests

Create server.test.js:

javascript
const request = require('supertest');
const app = require('./server');

describe('Cloud Native App', () => {
  test('Health check endpoint', async () => {
    const response = await request(app).get('/health');
    expect(response.status).toBe(200);
    expect(response.body.status).toBe('healthy');
  });

  test('Root endpoint', async () => {
    const response = await request(app).get('/');
    expect(response.status).toBe(200);
    expect(response.body.message).toContain('Cloud-Native');
  });

  test('API status endpoint', async () => {
    const response = await request(app).get('/api/status');
    expect(response.status).toBe(200);
    expect(response.body.api).toBe('running');
  });
});

Step 2: Create an Optimized Dockerfile

Create a production-ready Dockerfile using multi-stage builds:

dockerfile
# Multi-stage build for optimized production image
FROM node:18-alpine AS builder

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production && npm cache clean --force

# Production stage
FROM node:18-alpine AS production

# Create non-root user for security
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

# Set working directory
WORKDIR /app

# Copy built application from builder stage
COPY --from=builder /app/node_modules ./node_modules
COPY --chown=nextjs:nodejs . .

# Expose port
EXPOSE 3000

# Switch to non-root user
USER nextjs

# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# Start the application
CMD ["npm", "start"]

Create .dockerignore

text
node_modules
npm-debug.log*
.git
.gitignore
README.md
.env
.nyc_output
coverage
.docker
Dockerfile*
.dockerignore

Step 3: Build and Test Your Container

Build the Container Image

bash
# Build the image
docker build -t cloud-native-app:v1.0.0 .

# Build with build arguments for customization
docker build \
  --build-arg NODE_ENV=production \
  --build-arg APP_VERSION=1.0.0 \
  -t cloud-native-app:v1.0.0 .

Test the Container Locally

bash
# Run the container
docker run -d \
  --name cloud-app \
  --publish 3000:3000 \
  --env NODE_ENV=production \
  cloud-native-app:v1.0.0

# Test the application
curl http://localhost:3000
curl http://localhost:3000/health
curl http://localhost:3000/api/status

# View logs
docker logs cloud-app

# Stop and remove the container
docker stop cloud-app && docker remove cloud-app

Step 4: Optimize Your Images

Analyze Image Size

bash
# Check image size
docker images cloud-native-app:v1.0.0

# Analyze image layers
docker history cloud-native-app:v1.0.0

Use Docker Buildx for Advanced Builds

bash
# Create a new builder instance
docker buildx create --name cloud-builder --use

# Build multi-platform images
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --tag cloud-native-app:v1.0.0 \
  --push .

Step 5: Container Security Best Practices

Scan for Vulnerabilities

bash
# Scan the image for vulnerabilities
docker scout cves cloud-native-app:v1.0.0

# Generate a detailed security report
docker scout recommendations cloud-native-app:v1.0.0

Security Checklist

  • Non-root user: Container runs as non-root user
  • Minimal base image: Using Alpine Linux for smaller attack surface
  • Security headers: Application includes security middleware
  • Health checks: Container includes health check endpoints
  • Secrets management: No secrets embedded in image
  • Resource limits: Container respects memory and CPU limits

Step 6: Create Docker Compose for Development

Create docker-compose.yml for local development:

yaml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DEBUG=app:*
    volumes:
      - .:/app
      - /app/node_modules
    command: npm run dev
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    restart: unless-stopped

networks:
  default:
    name: cloud-native-network

Test with Docker Compose

bash
# Start all services
docker-compose up -d

# View logs
docker-compose logs -f

# Scale the application
docker-compose up -d --scale app=3

# Stop all services
docker-compose down

Testing and Validation

Performance Testing

bash
# Install Apache Bench for load testing
# macOS: brew install httpie
# Ubuntu: sudo apt install apache2-utils

# Run basic load test
ab -n 1000 -c 10 http://localhost:3000/

Container Resource Usage

bash
# Monitor container resource usage
docker stats cloud-app

# Check container processes
docker exec cloud-app ps aux

Best Practices Summary

Dockerfile Optimization

  1. Use multi-stage builds to reduce final image size
  2. Copy files in order of change frequency to leverage layer caching
  3. Run as non-root user for security
  4. Use specific image tags instead of ’latest'
  5. Include health checks for container orchestration

Security Best Practices

  1. Scan images regularly for vulnerabilities
  2. Use minimal base images (Alpine, distroless)
  3. Don’t embed secrets in images
  4. Sign and verify images in production
  5. Implement proper logging and monitoring

What’s Next?

Your application is now containerized and ready for deployment! In the next step, you’ll learn how to deploy your containerized application to Kubernetes using Helm charts, implement monitoring and logging, and set up a complete production deployment pipeline.

You’ll also explore advanced Kubernetes concepts like:

  • Pod autoscaling
  • Service mesh integration
  • GitOps deployment workflows
  • Production monitoring and alerting

Complete these tasks to finish this step:

You must complete all tasks in the checklist before marking this step as complete.