Skip to main content
Geni provides official Docker images for running migrations in containerized environments.

Quick Start

Run Geni using the official Docker image:
docker run --rm -it --network=host \
  -v "$(pwd)/migrations:/migrations" \
  -e DATABASE_URL="postgres://user:pass@localhost:5432/db" \
  ghcr.io/emilpriver/geni:latest up

Available Images

Geni provides two Docker image variants:

Full Image (Latest)

ghcr.io/emilpriver/geni:latest
The full image includes database-specific tools like pg_dump, mysqldump, and mariadb-dump for schema dumping functionality.

Slim Image

ghcr.io/emilpriver/geni:latest-slim
The slim image does not include database dump utilities. It will not attempt to dump schemas for MySQL, MariaDB, or PostgreSQL databases.

Image Configuration

The Docker images are built with the following configuration:
  • Base: Alpine Linux 3.19.1 (minimal footprint)
  • Binary location: /usr/src/app/geni
  • Default migrations folder: /migrations
  • Entrypoint: ./geni

Volume Mounting

To work with migrations on your host system, mount your migrations directory:
docker run --rm -it \
  -v "$(pwd)/migrations:/migrations" \
  ghcr.io/emilpriver/geni:latest up
The -v flag creates a bind mount, making your local migrations folder available inside the container at /migrations.

Common Use Cases

Apply Migrations

docker run --rm -it --network=host \
  -v "$(pwd)/migrations:/migrations" \
  -e DATABASE_URL="postgres://user:pass@localhost:5432/db" \
  ghcr.io/emilpriver/geni:latest up

Create New Migration

docker run --rm -it \
  -v "$(pwd)/migrations:/migrations" \
  -e DATABASE_URL="sqlite:///migrations/dev.db" \
  ghcr.io/emilpriver/geni:latest new create_users_table

Rollback Migrations

docker run --rm -it --network=host \
  -v "$(pwd)/migrations:/migrations" \
  -e DATABASE_URL="postgres://user:pass@localhost:5432/db" \
  ghcr.io/emilpriver/geni:latest down -a 2

Check Migration Status

docker run --rm -it --network=host \
  -v "$(pwd)/migrations:/migrations" \
  -e DATABASE_URL="postgres://user:pass@localhost:5432/db" \
  ghcr.io/emilpriver/geni:latest status

View Help

docker run --rm -it ghcr.io/emilpriver/geni:latest --help

Environment Variables

Configure Geni using environment variables:
docker run --rm -it --network=host \
  -v "$(pwd)/migrations:/migrations" \
  -e DATABASE_URL="postgres://user:pass@localhost:5432/db" \
  -e DATABASE_MIGRATIONS_FOLDER="/migrations" \
  -e DATABASE_MIGRATIONS_TABLE="schema_migrations" \
  -e DATABASE_WAIT_TIMEOUT="60" \
  -e DATABASE_SCHEMA_FILE="schema.sql" \
  ghcr.io/emilpriver/geni:latest up

Docker Compose

Integrate Geni into your Docker Compose workflow:

Basic Example

version: "3.9"

services:
  db:
    image: postgres:14
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"
  
  migrations:
    image: ghcr.io/emilpriver/geni:latest
    depends_on:
      - db
    volumes:
      - ./migrations:/migrations
    environment:
      DATABASE_URL: postgres://postgres:password@db:5432/app?sslmode=disable
      DATABASE_WAIT_TIMEOUT: "60"
    command: up
Run migrations:
docker compose up migrations

Multi-Database Setup

version: "3.9"

services:
  postgres:
    image: postgres:14
    environment:
      POSTGRES_DB: development
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: mysecretpassword
    ports:
      - "5432:5432"
  
  mysql:
    image: mysql:latest
    environment:
      MYSQL_DATABASE: development
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
  
  geni-postgres:
    image: ghcr.io/emilpriver/geni:latest
    depends_on:
      - postgres
    volumes:
      - ./migrations/postgres:/migrations
    environment:
      DATABASE_URL: postgres://postgres:mysecretpassword@postgres:5432/development?sslmode=disable
      DATABASE_WAIT_TIMEOUT: "60"
    command: up
  
  geni-mysql:
    image: ghcr.io/emilpriver/geni:latest
    depends_on:
      - mysql
    volumes:
      - ./migrations/mysql:/migrations
    environment:
      DATABASE_URL: mysql://root:password@mysql:3306/development
      DATABASE_WAIT_TIMEOUT: "60"
    command: up

Development Workflow

Create a development setup with automatic migrations:
version: "3.9"

services:
  db:
    image: postgres:14
    environment:
      POSTGRES_DB: dev
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dev"]
      interval: 10s
      timeout: 5s
      retries: 5
  
  migrations:
    image: ghcr.io/emilpriver/geni:latest
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - ./migrations:/migrations
    environment:
      DATABASE_URL: postgres://dev:dev@db:5432/dev?sslmode=disable
      DATABASE_WAIT_TIMEOUT: "30"
    command: up
  
  app:
    build: .
    depends_on:
      migrations:
        condition: service_completed_successfully
    environment:
      DATABASE_URL: postgres://dev:dev@db:5432/dev?sslmode=disable
    ports:
      - "3000:3000"

volumes:
  postgres_data:

Networking Considerations

Host Network Mode

When connecting to databases on your host machine:
docker run --rm -it --network=host \
  -v "$(pwd)/migrations:/migrations" \
  -e DATABASE_URL="postgres://user:pass@localhost:5432/db" \
  ghcr.io/emilpriver/geni:latest up

Bridge Network

When using Docker Compose or custom networks, use service names:
docker run --rm -it --network=my_network \
  -v "$(pwd)/migrations:/migrations" \
  -e DATABASE_URL="postgres://user:pass@postgres:5432/db" \
  ghcr.io/emilpriver/geni:latest up

Building Custom Images

You can build custom Geni images from the source:

Dockerfile Reference

FROM rust:1.89.0-alpine3.22 as builder
WORKDIR /usr/src/app
COPY . .

RUN apk add musl-dev
RUN cargo build --release
RUN cp target/release/geni /usr/src/app/geni

FROM alpine:3.19.1
COPY --from=builder /usr/src/app/geni /usr/src/app/geni

ENV DATABASE_MIGRATIONS_FOLDER="/migrations"

LABEL org.opencontainers.image.description="Geni: Standalone database migration tool"

WORKDIR /usr/src/app

ENTRYPOINT ["./geni"]

Build Custom Image

# Clone the repository
git clone https://github.com/emilpriver/geni.git
cd geni

# Build the image
docker build -t my-geni:latest .

# Run your custom image
docker run --rm -it my-geni:latest --help

Best Practices

1

Use Volume Mounts for Migrations

Always mount your migrations directory to ensure changes persist and can be version controlled.
2

Set Appropriate Timeouts

Use DATABASE_WAIT_TIMEOUT when databases need initialization time, especially in Docker Compose setups.
3

Use Health Checks

In Docker Compose, use health checks to ensure databases are ready before running migrations.
4

Choose the Right Image

Use the slim image if you don’t need schema dumping to reduce image size and attack surface.
5

Manage Secrets Securely

Use Docker secrets or environment files for sensitive database credentials, not command-line arguments.

Troubleshooting

Use --network=host to access databases running on your host machine, or use the appropriate service name in Docker networks.
Ensure you’re mounting the correct local directory to /migrations in the container:
-v "$(pwd)/migrations:/migrations"
Check file permissions on your host system. The container runs as root by default, but file ownership may cause issues.
The slim image doesn’t include dump utilities. Use the full image (latest) if you need schema dumping:
ghcr.io/emilpriver/geni:latest

Build docs developers (and LLMs) love