Docker voor Finch

Finch is ontworpen om goed te draaien binnen Docker. De officiële uproid/finch-image bevat de Dart SDK, de finch-CLI en alles wat nodig is om een Finch-applicatie te bouwen en te serveren. Het example/-project in deze repository levert een volledige docker-compose.yaml die je kunt kopiëren als uitgangspunt voor MongoDB, MySQL, SQLite, een Nginx reverse proxy en het bouwen van Tailwind-assets — allemaal in één stack.

Projectstructuur

Een typisch Docker-gebaseerd Finch-project gebruikt deze bestanden:

my-app/
  Dockerfile             # Bouwt de Finch-containerimage
  docker-compose.yaml    # Orkestreert alle services
  docker/
    nginx.conf           # Nginx reverse-proxyconfiguratie
  lib/                   # Applicatie Dart-code
  public/                # Statische bestanden geserveerd door Nginx
  sqlite/                # SQLite-databasebestanden (volume-mounted)
  mongodb/               # MongoDB-databestanden (volume-mounted)
  mysql/                 # MySQL-databestanden (volume-mounted)

Dockerfile

De voorbeeld-Dockerfile gebruikt de officiële Finch-basisimage en installeert afhankelijkheden tijdens het bouwen:

FROM uproid/finch:latest AS build
WORKDIR /www

# Standaardwaarden voor paden/gedrag — overschrijf per omgeving in docker-compose.yaml
ENV WIDGETS_PATH=./lib/widgets
ENV WIDGETS_TYPE=j2.html
ENV LANGUAGE_PATH=./lib/languages
ENV PUBLIC_DIR=./public
ENV LOCAL_DEBUG=true
ENV ENABLE_DATABASE=true

COPY pubspec.yaml ./
RUN dart pub get --no-offline
COPY lib/ ./lib/

# Build: genereert de finch-appbinary en compileert routes/widgets vooraf
RUN finch -u

# App-poort (8085) en de Dart VM-service/DevTools-poort (8181)
EXPOSE 8085 8181

# Bij het starten: voer de app uit en pas openstaande MySQL- en SQLite-migraties toe
CMD ["finch", "serve", "-p", "/www/lib/watcher.dart", "--args=\"migrate --init --and migrate_sqlite --init\""]

finch -u (zie Finch CLI) heractiveert de globaal geïnstalleerde finch-CLI zelf — gelijk aan het opnieuw uitvoeren van dart pub global activate finch — zodat de build-/serve-tooling in de image overeenkomt met de laatst gepubliceerde Finch-release, in plaats van de versie die toevallig in de uproid/finch:latest-basisimage is ingebakken. De CMD voert de app uit via finch serve, houdt lib/watcher.dart in de gaten (zie Run App) en geeft migrate --init / migrate_sqlite --init door zodat databasemigraties (zie Database Migration) automatisch worden toegepast bij elke containerstart — veilig om herhaaldelijk uit te voeren, aangezien al toegepaste migraties worden overgeslagen.

Als je de local debugger (ENABLE_LOCAL_DEBUGGER=true) binnen een container inschakelt, voeg dan ook EXPOSE 8282 toe en publiceer deze in docker-compose.yaml — die poort draagt de interactieve terminal van de debugger via een WebSocket en wordt standaard niet blootgesteld.

Opmerking: de root-Dockerfile en docker-compose-live.yaml in deze repository bouwen het Finch-framework zelf voor het eigen live voorbeeld en zijn niet bedoeld om te kopiëren naar een applicatieproject — begin altijd bij example/Dockerfile en example/docker-compose.yaml.

docker-compose.yaml

De volledige voorbeeldstack bestaat uit vijf services: finch (je app), nginx (reverse proxy), mongodb, mysql en nodejs (voor het bouwen van Tailwind CSS-assets):

services:
  finch:
    hostname: finch
    container_name: finch
    image: uproid/finch:latest
    restart: always
    ports:
      - "8085:8085"  # App-poort
      - "8181:8181"  # Dart VM-service / DevTools-poort
    environment:
      SQLITE_PATH: /www/sqlite/example_database.sqlite
      ENABLE_DATABASE: true
      MONGODB_CONNECTION: mongodb   # Gebruik de servicehostnaam, niet localhost
      MONGODB_PORT: 27017           # Interne containerpoort (niet de naar de host gemapte 27018)
      MONGODB_NAME: my_database
      MONGODB_AUTH: admin
      MONGODB_PASSWORD: change-me
      MONGODB_USER: root
      MYSQL_HOST: mysql
      MYSQL_PORT: 3306
      MYSQL_DATABASE: my_database
      MYSQL_USER: app_user
      MYSQL_PASSWORD: change-me
    build:
      context: .
    volumes:
      - ./lib/:/www/lib/           # Hot-reload broncode zonder de image opnieuw te bouwen
      - ./public/:/www/public/
      - ./sqlite/:/www/sqlite/     # SQLite-data behouden over containerherstarts heen

  nodejs:
    image: node:20-alpine
    container_name: nodejs
    working_dir: /app
    tty: true
    stdin_open: true
    volumes:
      - ./:/app
    command: sh -c "npm install && npm run tailwind:watch"
    restart: always

  nginx:
    image: nginx:latest
    container_name: nginx
    restart: always
    ports:
      - "8080:8080"  # Publiek toegankelijke poort
    volumes:
      - ./public/:/var/www/html/public
      - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf

  mongodb:
    image: mongo:latest
    container_name: mongodb
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: change-me
    ports:
      - "27018:27017"   # Hostpoort 27018 om conflicten met een lokale MongoDB-installatie te voorkomen
    volumes:
      - ./mongodb:/data/db

  mysql:
    image: mysql:latest
    container_name: mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: change-me-root
      MYSQL_DATABASE: my_database
      MYSQL_USER: app_user
      MYSQL_PASSWORD: change-me
    ports:
      - "3306:3306"
    volumes:
      - ./mysql:/var/lib/mysql

Belangrijke punten:

  • nodejs is alleen relevant als je project Tailwind gebruikt (zie de package.json en tailwind.config.js van het voorbeeld); laat deze service volledig weg als je geen frontend-assets bouwt.
  • mongodb en mysql zijn onafhankelijk van elkaar — schakel alleen de databases in die je app daadwerkelijk gebruikt door enable: false in te stellen op de bijbehorende FinchDBConfig/FinchMysqlConfig/FinchSqliteConfig in je configs.dart (zie Configuration), en verwijder hier simpelweg het ongebruikte serviceblok.
  • Binnen het Docker-netwerk bereiken containers elkaar via de servicenaam (mongodb, mysql, nginx), nooit via localhost — daarom wijzen MONGODB_CONNECTION en MYSQL_HOST naar de servicenamen en niet naar localhost, ook al zou hetzelfde .env-bestand dat wordt gebruikt voor lokale (niet-Docker) ontwikkeling localhost bevatten.
  • De naar de host gemapte MongoDB-poort (27018) is anders dan de poort waarmee Finch intern verbinding maakt (27017) — de app praat met Mongo over het Docker-netwerk op de eigen poort, terwijl 27018 alleen bestaat zodat je vanaf je hostmachine een lokale MongoDB-client kunt verbinden met localhost:27018 om te debuggen.

Nginx reverse proxy

docker/nginx.conf proxyt zowel reguliere HTTP-verzoeken als de WebSocket-gebaseerde /ws- en /debugger-endpoints (gebruikt door WebSockets en de local debugger) naar de finch-container:

upstream finch {
    server finch:8085;
    keepalive 32;
}

server {
    listen 8080;
    server_name localhost:8080;
    client_max_body_size 20M;

    location / {
        root /var/www/html;
        try_files /public$uri @dart;
    }

    # WebSocket-ondersteuning voor /ws- en /debugger-paden
    location ~ ^/(ws|debugger) {
        proxy_pass http://finch;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;
        proxy_set_header Host $host;
        proxy_buffering off;
        proxy_read_timeout 3600s;
    }

    location @dart {
        proxy_pass http://finch;
        proxy_pass_request_body on;
        proxy_pass_request_headers on;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

try_files /public$uri @dart betekent dat Nginx statische bestanden rechtstreeks serveert vanuit de gemounte public/-map als ze bestaan, en het verzoek alleen doorstuurt naar Finch (@dart) als dat niet zo is — dit is dezelfde Nginx for Finch-configuratie die buiten Docker wordt gebruikt, maar dan gericht op de finch-container in plaats van een lokaal proces. proxy_buffering off en de lange timeouts in het WebSocket location-blok zijn vereist — zonder deze zal Nginx langlopende WebSocket-verbindingen bufferen of voortijdig sluiten.

De stack starten

# Bouw en start alle services
docker-compose up --build

# Op de achtergrond uitvoeren (detached)
docker-compose up -d --build

# Logs van één service volgen
docker-compose logs -f finch

# Bouw alleen de finch-service opnieuw na een Dockerfile-wijziging
docker-compose up -d --build finch

# Stop alles (alleen containers — volumes blijven behouden)
docker-compose down

# Stop alles EN verwijder volumes (verwijdert alle databasedata)
docker-compose down -v

Zodra deze draait, is de app bereikbaar via http://localhost:8080 (via Nginx) of rechtstreeks via http://localhost:8085 (Nginx omzeilend, handig bij het debuggen of een probleem in Finch of in de proxyconfiguratie zit).

Omdat ./lib/ en ./public/ als bind-mount in de container zijn gekoppeld, worden wijzigingen aan die bestanden op de host onmiddellijk opgemerkt door Finch's file watcher — je hoeft de image niet opnieuw te bouwen voor dagelijkse Dart-codewijzigingen, alleen wanneer de afhankelijkheden in pubspec.yaml veranderen.

Omgevingsvariabelen

Elke FinchConfigs-waarde die wordt gelezen via env.get(...) (of env.getInt(...) / env.getBool(...)) kan worden ingesteld als Docker-omgevingsvariabele of via een .env-bestand dat door de app wordt geladen. Een minimale .env die overeenkomt met het bovenstaande compose-bestand:

MONGODB_CONNECTION=mongodb
MONGODB_PORT=27017
MONGODB_NAME=my_database
MONGODB_AUTH=admin
MONGODB_USER=root
MONGODB_PASSWORD=change-me

MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_DATABASE=my_database
MYSQL_USER=app_user
MYSQL_PASSWORD=change-me

SQLITE_PATH=/www/sqlite/example_database.sqlite

ENABLE_LOCAL_DEBUGGER=false
LOCAL_DEBUG=false

Zie Configuration voor de volledige lijst met ondersteunde omgevingsvariabelen, inclusief server-, sjabloon- en mailinstellingen die hier niet worden getoond.

Commit nooit echte wachtwoorden. Geef in docker-compose.yaml zelf de voorkeur aan ${VAR_NAME}-interpolatie vanuit een .env-bestand dat via .gitignore is uitgesloten, in plaats van geheimen rechtstreeks hard te coderen in de YAML zoals hierboven omwille van de beknoptheid is getoond.

Overwegingen voor productie

  • Zet ENABLE_LOCAL_DEBUGGER=false en LOCAL_DEBUG=false in productie — de local debugger stelt een WebSocket-terminal naar de draaiende container bloot en is alleen bedoeld voor ontwikkeling. Zie Debugging.
  • Publiceer de Dart VM-service-poort (8181) niet buiten je eigen netwerk; deze geeft volledige lees-/schrijftoegang tot de draaiende isolate.
  • Plaats een echte TLS-terminerende reverse proxy (Nginx met een certificaat, of een managed load balancer) vóór de stack — de voorbeeld-nginx.conf hier is alleen HTTP en bedoeld als uitgangspunt.
  • Gebruik named volumes (of een managed database) voor mongodb/mysql-data in productie in plaats van een host-directory te bind-mounten, zodat back-ups en rechten consistent worden afgehandeld over hosts heen.
  • Verhoog maxConnections op FinchDBConfig/FinchMysqlConfig als je verder opschaalt dan een handvol gelijktijdige gebruikers — zie MongoDB en MySQL.