Hanami 2 ist erschienen, aber die Dokumentation zur Docker-Integration ist dünn. Wer eine Hanami-API mit PostgreSQL aufsetzen will, ohne lokale Abhängigkeiten zu installieren, findet hier eine komplette Anleitung.
Voraussetzungen: Ruby und Hanami installieren
Ruby muss mindestens in Version 3.0.0 vorliegen. Dann den hanami Gem installieren:
gem install hanami
Neue Hanami-Anwendung erstellen:
hanami new simple_api
Dieser Befehl erstellt eine Reihe von Dateien im Verzeichnis simple_api.
Wie sieht das Dockerfile aus?
FROM ruby:3.2.1-alpine
RUN apk add --update build-base ruby-dev
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . .
EXPOSE 2300
CMD ["hanami", "server"]
Wir verwenden ruby:3.2.1-alpine, um ein schlankes Image zu erhalten. Dafür sind build-base und ruby-dev nötig. Hanami verwendet standardmässig Port 2300 (nicht 3000 wie Rails).
Image bauen:
docker build -t simple-api -f Dockerfile.dev .
Wie verbindet Docker Compose App und Datenbank?
# simple_api/docker-compose.yaml
version: '3.8'
volumes:
postgres-data:
services:
db:
image: postgres
volumes:
- postgres-data:/var/lib/postgresql/data
env_file: .env
app:
image: simple-api
command: sh -c "hanami server"
ports:
- "2300:2300"
depends_on:
- db
env_file: .env
Zwei Dienste: db für PostgreSQL und app für den Webserver. Die Umgebungsvariable POSTGRES_PASSWORD gehört in die .env-Datei:
# simple_api/.env
POSTGRES_PASSWORD=XYZ123QWE
Container starten:
docker-compose up
Wie wird die Datenbankverbindung konfiguriert?
Zuerst die nötigen Gems zum Gemfile hinzufügen:
# simple_api/Gemfile
gem "rom", "~> 5.3"
gem "rom-sql", "~> 3.6"
gem "pg"
Dann postgresql-dev im Dockerfile ergänzen:
RUN apk add --update build-base ruby-dev postgresql-dev
Container stoppen, Image neu bauen, Container neu starten. Dann die Datenbank erstellen:
docker-compose exec db psql -U postgres -c "CREATE DATABASE simple_api_development;"
Jetzt den Persistenzanbieter in Hanami konfigurieren:
# simple_api/config/providers/persistence.rb
Hanami.app.register_provider :persistence, namespace: true do
prepare do
require "rom"
config = ROM::Configuration.new(:sql, target["settings"].database_url)
register "config", config
register "db", config.gateways[:default].connection
end
start do
config = target["persistence.config"]
config.auto_registration(
target.root.join("lib/simple_api/persistence"),
namespace: "SimpleAPI::Persistence"
)
register "rom", ROM.container(config)
end
end
target["settings"].database_url verweist auf die Umgebungsvariable DATABASE_URL. Diese in .env ergänzen:
# simple_api/.env
DATABASE_URL=postgres://postgres:XYZ123QWE@db:5432/simple_api_development
POSTGRES_PASSWORD=XYZ123QWE
Die Einstellung database_url in der Settings-Klasse registrieren:
# simple_api/config/settings.rb
module SimpleAPI
class Settings < Hanami::Settings
setting :database_url, constructor: Types::String
end
end
Image neu bauen und Container neu starten.
Funktioniert die Verbindung?
Zum Test: Migration erstellen, Daten einfügen, Daten lesen.
Rake-Migrationen aktivieren:
# simple_api/Rakefile
require "rom/sql/rake_task"
task :environment do
require_relative "config/app"
require "hanami/prepare"
end
namespace :db do
task setup: :environment do
Hanami.app.prepare(:persistence)
ROM::SQL::RakeSupport.env = Hanami.app["persistence.config"]
end
end
Migration für eine Tabelle:
# simple_api/db/migrate/20230228200134_create_books.rb
ROM::SQL.migration do
change do
create_table :books do
primary_key :id
column :title, :text, null: false
column :author, :text, null: false
end
end
end
Relation definieren:
# simple_api/lib/simple_api/persistence/relations/books.rb
module SimpleAPI
module Persistence
module Relations
class Books < ROM::Relation[:sql]
schema(:books, infer: true)
end
end
end
end
Image neu bauen, Container starten, dann in die Shell verbinden:
docker-compose exec app sh
bundle exec rake db:migrate
hanami console
app["persistence.rom"].relations[:books].insert(title: 'The Alloy of Law', author: 'Brandon Sanderson')
app["persistence.rom"].relations[:books].to_a
Die Hanami-Anwendung ist mit PostgreSQL verbunden. Weitere Informationen im Hanami 2.0 Getting Started Guide.
Praktische Umsetzung: Der USEO-Ansatz
Docker Compose ist bei uns Standard für lokale Entwicklungsumgebungen. Drei Erkenntnisse aus dem produktiven Einsatz:
- Health Checks statt
depends_on. Diedepends_on-Direktive wartet nur auf den Container-Start, nicht auf die Datenbank-Bereitschaft. Wir verwenden Health Checks oder ein Wrapper-Skript (wait-for-it.sh), das die Datenbankverbindung prüft, bevor der App-Server startet. - Volumes für Gem-Cache. Statt bei jedem Image-Build alle Gems neu zu installieren, mounten wir ein Volume für das Bundle-Verzeichnis. Das reduziert die Build-Zeit bei Gem-Änderungen von Minuten auf Sekunden.
- Separate Compose-Dateien pro Umgebung.
docker-compose.ymlfür die Basiskonfiguration,docker-compose.dev.ymlfür Entwickler-Overrides (z.B. Source-Code-Mounting, Debug-Ports). Das hält die Konfiguration sauber und erlaubt umgebungsspezifische Anpassungen.