Docker - Dockerfile og Compose

Skip to content

Bygg og kjør dine egne applikasjoner

I Level 1 brukte du et ferdig image og koblet en lokal mappe til en Nginx-container med en bind mount. Her bygger du et image fra din egen Flask-applikasjon. Først lærer du Dockerfile, og deretter bruker du Docker Compose til å beskrive både containeren og en bind mount.

Har du ikke en Flask-applikasjon fra før?

Bruk 🍾 Flask 1 eller 🍾 Flask 2 API som utgangspunkt før du fortsetter her.

Lag et image med Dockerfile

En Dockerfile beskriver trinnene Docker skal bruke for å bygge et image. Hver linje gjør noe bestemt:

FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
EXPOSE 5000
CMD ["python", "app.py"]

Docker bygger instruksjonene som lag. Tenk på imaget som en løk: Docker legger på ett lag om gangen, fra innerst til ytterst. Når et lag endres, må Docker bygge dette laget og alle lagene etter det på nytt, mens lagene innenfor kan hentes fra build cache.

Derfor kopierer vi requirements.txt alene før COPY . .: den tidkrevende installasjonen ligger innerst, mens kildekoden som endres ofte ligger ytterst:

flowchart TB
    L1["Lag 1: FROM python:3.12-slim<br/>innerst"] --> L2["Lag 2: WORKDIR /app"]
    L2 --> L3["Lag 3: COPY requirements.txt ."]
    L3 --> L4["Lag 4: RUN pip install"]
    L4 --> L5["Lag 5: COPY . .<br/>ytterst"]
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

Hvis requirements.txt endres, må Docker installere pakkene på nytt. Det er ønsket: den nye pakken skal faktisk komme med i imaget. Hvis bare kildekoden endres, kan Docker bruke det ferdige installasjonslaget og bygge videre fra COPY . ..

Når Docker publiserer en port, videresender Docker trafikken til programmet inne i containeren. Programmet må derfor lytte på containerens nettverk, ikke bare på 127.0.0.1, som betyr “denne containeren”. Hvis programmet bare lytter på 127.0.0.1, kan ikke andre maskiner nå det gjennom Docker-porten.

Flask-eksempel

Flask må lytte på alle nettverksgrensesnitt (0.0.0.0) når den kjører i container. 0.0.0.0 betyr at Flask tar imot forespørsler på alle nettverkskort inne i containeren:

app.run(host="0.0.0.0", port=5000)

Bygg og start:

docker build -t min-app:latest .
docker run -d --name min-app -p 5000:5000 min-app:latest
docker logs -f min-app

Åpne http://SERVER-IP:5000 fra en annen maskin. I -p 5000:5000 betyr det første tallet porten på serveren, mens det andre er porten inne i containeren. Hvis det ikke virker, sjekk først docker ps, loggene og at porten er riktig publisert.

Bruk Docker Compose

Docker Compose gjør det enklere å beskrive og starte tjenesten på nytt. Filen compose.yaml blir en oppskrift på hvordan containeren skal kjøres. Lag denne i samme mappe som Dockerfile:

services:
  app:
    build: .
    container_name: min-app
    ports:
      - "5000:5000"
    volumes:
      - ./app-data:/app/data
    restart: unless-stopped

Ny på YAML?

📄 YAML forklarer nøkkel-verdi-par, lister og innrykk som compose.yaml bruker.

Lag mappen før du starter tjenesten:

mkdir -p ./app-data

I ./app-data:/app/data er ./app-data mappen på serveren, mens /app/data er mappen inne i containeren. ./ viser at Compose skal bruke en lokal mappe fra katalogen du står i. Dataene blir derfor liggende på serveren også etter at du kjører docker compose down.

Applikasjonen må faktisk skrive data til /app/data for at bind mounten skal ha effekt. Filer som lagres andre steder i containeren, for eksempel direkte i prosjektmappen uten en volumkobling, kan forsvinne når containeren bygges eller opprettes på nytt.

Start tjenesten og se innholdet i den lokale mappen:

docker compose up -d --build
docker compose ps
docker compose logs -f
docker compose down

Oppdatering

Hent ny kode, bygg imaget på nytt og start tjenesten:

git pull
docker compose up -d --build

Ny på Git i terminalen?

🌱 Git i terminalen viser git clone, git commit og git push steg for steg.

Hvis applikasjonen skal lagre data, må dataene ligge i en database eller et volume. Filer som bare ligger inne i containeren kan forsvinne når containeren bygges på nytt. Dette er viktig hvis brukere for eksempel legger inn sitater eller stemmer. I en produksjonsløsning må du i tillegg planlegge sikkerhetskopiering av volumet.

API-nøkler, passord og annen sensitiv informasjon

Med sensitiv informasjon mener vi verdier som ikke skal deles offentlig, for eksempel et API-token, et databasepassord eller passordet til en administratorkonto. Dersom en slik verdi legges i kildekoden og pushes til GitHub, må du regne med at den er kjent selv om du sletter linjen senere.

Bruk miljøvariabler i stedet:

import os

api_key = os.environ["WEATHER_API_KEY"]

I Compose kan verdien ligge i en lokal .env-fil:

WEATHER_API_KEY=lim inn din lokale nøkkel her

Bruk den i compose.yaml med ${WEATHER_API_KEY}. .env skal ikke pushes til GitHub. Lag heller en .env.example uten ekte verdier, slik at andre ser hvilke variabler de må lage:

WEATHER_API_KEY=din_nokkel_her

Hvis du ved et uhell har delt en nøkkel, skal du ikke bare slette filen. Deaktiver eller bytt nøkkelen hos tjenesten først, og spør lærer om hjelp.

.env kan havne inne i imaget, ikke bare i kildekoden

COPY . . i Dockerfile-en kopierer alt i mappen inn i imaget, .env inkludert, hvis ikke annet er sagt. Da ligger den sensitive informasjonen lagret i selve imaget, og følger med hvis imaget noensinne deles eller lastes opp et sted, selv om du aldri pusher .env til GitHub.

Legg derfor til en .dockerignore-fil i samme mappe som Dockerfile:

.env
.git
__pycache__/

Neste steg

Når du har bygget og kjørt din egen container med Compose, gå videre til Docker - Publisere applikasjon. Der lærer du å publisere tjenesten med et domenenavn gjennom en reverse proxy.