Vibe coding with AI course · Module 13: Twitch Clone

Docker & Containers

1 min read
In this lesson2

Containerizing a Next.js app.

Dockerfile

FROM node:24-alpine AS base

FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

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

Docker Compose

version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://...

See you soon!

Code for this lesson: App.tsx
'use client';

interface StreamCardProps {
  title: string;
  category: string;
  viewerCount: number;
  channelName: string;
  thumbnail: string;
}

const mockStreams: StreamCardProps[] = [
  {
    title: 'Coding a Pirate Ship from Scratch!',
    category: 'Software & Game Development',
    viewerCount: 1247,
    channelName: 'Captain Redbeard',
    thumbnail: 'https://images.unsplash.com/photo-1544551763-46a013bb70d5?w=400&h=225&fit=crop'
  },
  {
    title: 'JavaScript Tutorial - Building React Apps',
    category: 'Software & Game Development',
    viewerCount: 892,
    channelName: 'Sailor Sam',
    thumbnail: 'https://images.unsplash.com/photo-1517694712202-14dd9538aa97?w=400&h=225&fit=crop'
  },
  {
    title: 'TypeScript Advanced Patterns',
    category: 'Science & Technology',
    viewerCount: 567,
    channelName: 'Navigator Nancy',
    thumbnail: 'https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=400&h=225&fit=crop'
  }
];

function StreamCard({ stream }: { stream: StreamCardProps }) {
  return (
    <div className="bg-gray-900 rounded overflow-hidden hover:scale-105 transition cursor-pointer">
      <div className="relative">
        <img
          src={stream.thumbnail}
          alt={stream.title}
          className="w-full aspect-video object-cover"
        />
        <div className="absolute top-2 left-2 px-2 py-1 bg-red-600 text-white text-xs font-bold rounded">
          LIVE
        </div>
        <div className="absolute bottom-2 left-2 px-2 py-1 bg-black/70 text-white text-xs rounded">
          {stream.viewerCount.toLocaleString()} viewers
        </div>
      </div>

      <div className="p-3">
        <h3 className="font-semibold mb-1 line-clamp-2">{stream.title}</h3>
        <p className="text-sm text-gray-400 mb-1">{stream.channelName}</p>
        <p className="text-xs text-gray-500">{stream.category}</p>
      </div>
    </div>
  );
}

export default function StreamGrid() {
  return (
    <div className="min-h-screen bg-gray-950 text-white p-8">
      <h1 className="text-3xl font-bold mb-6">Live Channels</h1>

      <div className="grid grid-cols-3 gap-4">
        {mockStreams.map((stream, i) => (
          <StreamCard key={i} stream={stream} />
        ))}
      </div>
    </div>
  );
}

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. Why do we containerize a Next.js app with Docker?

  2. 2. Which base image does the Dockerfile from the lesson use?

Hands-on tasks in the game

  • Vertical ordering

    Order the Dockerfile stages from the top of the file:

  • Horizontal ordering

    Arrange the port mapping in docker-compose (one line): "3000:3000"

Useful articles