Tooling Lab
45 minutes
docker
nextjs
api
hands-on

Dockerized Web App + API

Build and deploy a containerized Next.js application with API routes using AI assistance.

Overview

In this lab, you'll build a complete containerized web application:

  • Next.js frontend with Tailwind CSS
  • API routes for backend logic
  • Docker development and production configurations
  • Health checks and environment management

Prerequisites

  • Docker and Docker Compose installed
  • Node.js 18+ (for local development)
  • Basic familiarity with Next.js

Step 1: Project Setup

Step 2: Create API Route

Create a simple API endpoint:

typescript
// app/api/health/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json({
    status: 'healthy',
    timestamp: new Date().toISOString(),
    environment: process.env.NODE_ENV,
  });
}

Step 3: Docker Development Setup

Create the development Dockerfile:

dockerfile
# Dockerfile.dev
FROM node:22-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

EXPOSE 3000

ENV NODE_ENV=development
ENV NEXT_TELEMETRY_DISABLED=1

CMD ["npm", "run", "dev"]

Create docker-compose.yml:

yaml
# docker-compose.yml
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
      - /app/.next
    environment:
      - NODE_ENV=development
      - NEXT_TELEMETRY_DISABLED=1

Step 4: Run Development Container

Step 5: Production Dockerfile

Create an optimized production build:

dockerfile
# Dockerfile.prod
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

EXPOSE 3000
CMD ["node", "server.js"]

Update next.config.ts for standalone output:

typescript
// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  output: 'standalone',
};

export default nextConfig;

Step 6: Production Compose

yaml
# docker-compose.prod.yml
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile.prod
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    restart: always

Step 7: Test Production Build

bash
# Build and run production container
docker-compose -f docker-compose.prod.yml up --build

# Verify health check
curl http://localhost:3000/api/health

Verification Checklist

  • Development container starts with hot reload
  • API health endpoint returns JSON
  • Production build completes without errors
  • Production image is under 200MB
  • Health check passes

Common Issues

IssueSolution
Hot reload not workingEnsure volumes are mounted correctly
Build fails on standaloneAdd output: 'standalone' to next.config.ts
Permission deniedCheck Docker Desktop is running

Next Steps

  • Add environment variable management with .env files
  • Set up CI/CD pipeline for automatic builds
  • Add database container with docker-compose