Kurs vibe coding z AI · Moduł 13: Twitch Clone

Docker & Containers

1 min czytania
W tej lekcji2

Konteneryzacja 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://...

Do zobaczenia!

Kod do tej lekcji: App.tsx
'use client';

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

const mockStreams: StreamCardProps[] = [
  {
    title: 'Kodujemy piracki statek od zera!',
    category: 'Tworzenie aplikacji i gier',
    viewerCount: 1247,
    channelName: 'Kapitan Redbeard',
    thumbnail: 'https://images.unsplash.com/photo-1544551763-46a013bb70d5?w=400&h=225&fit=crop'
  },
  {
    title: 'Kurs JavaScriptu - budujemy aplikacje w React',
    category: 'Tworzenie aplikacji i gier',
    viewerCount: 892,
    channelName: 'Marynarz Sam',
    thumbnail: 'https://images.unsplash.com/photo-1517694712202-14dd9538aa97?w=400&h=225&fit=crop'
  },
  {
    title: 'Zaawansowane wzorce w TypeScripcie',
    category: 'Nauka i technologia',
    viewerCount: 567,
    channelName: 'Nawigatorka 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">
          NA ŻYWO
        </div>
        <div className="absolute bottom-2 left-2 px-2 py-1 bg-black/70 text-white text-xs rounded">
          {stream.viewerCount.toLocaleString()} widzów
        </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">Kanały na żywo</h1>

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

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Po co konteneryzujemy aplikację Next.js w Dockerze?

  2. 2. Jaki obraz bazowy stosuje Dockerfile z lekcji?

Zadania praktyczne w grze

  • Układanie w pionie

    Uporządkuj etapy (stages) Dockerfile od góry pliku:

  • Układanie w poziomie

    Ułóż mapowanie portu w docker-compose (jedna linia): "3000:3000"

Przydatne artykuły