Skip to main content

Docker Deployment

Forja ships with a multi-stage Dockerfile that builds both the React admin dashboard and the Rust backend into a single, minimal production image.

Quick Start

The fastest way to run Forja in production:

# 1. Generate a .env with secure random secrets
./scripts/forja-init.sh

# 2. Start everything
docker compose -f docker-compose.prod.yml up -d

This starts PostgreSQL, Redis, and Forja with health checks, persistent volumes, and auto-configured database extensions. No source code checkout required beyond the compose file and init script.

Docker Hub

Pre-built images are published to Docker Hub on every push to main. Multi-platform images are available for linux/amd64 and linux/arm64.

docker pull dominikdorfstetter/forja

Images are tagged with:

  • latest — the most recent build from main
  • Git SHA (e.g. bf3df6d) — for pinning to a specific commit

Prerequisites

Multi-Stage Build Overview

The Dockerfile uses three stages to keep the final image small:

StageBase ImagePurpose
admin-buildnode:20-alpineInstalls npm dependencies and builds the React admin dashboard
backend-buildrust:1.93-bookwormCompiles the Rust backend in release mode, embedding the admin static files
runtimedebian:bookworm-slimMinimal runtime with only ca-certificates, libssl3, and libpq5

The final image contains a single binary (forja), the compiled admin dashboard static files, and the SQLx migration files.

Building the Image

From the repository root:

docker build -t forja .

The first build takes approximately 10-15 minutes due to Rust compilation. Subsequent builds benefit from Docker layer caching.

Build Optimizations

The Dockerfile sets two environment variables to reduce memory usage during Rust compilation:

ENV CARGO_PROFILE_RELEASE_LTO=thin
ENV CARGO_PROFILE_RELEASE_CODEGEN_UNITS=2

These settings prevent out-of-memory errors on machines with limited RAM (e.g., 2 GB CI runners or cloud build environments).

Running with Docker Compose

The repository includes docker-compose.prod.yml, a standalone compose file that uses the pre-built Docker Hub image. It does not require the source code -- just the compose file and a .env:

# Generate .env with secure random secrets
./scripts/forja-init.sh

# Start all services
docker compose -f docker-compose.prod.yml up -d

The production compose auto-constructs DATABASE_URL from the PostgreSQL credentials in your .env, so you only need to set POSTGRES_PASSWORD once.

Source Build Compose

If you prefer to build from source, the default docker-compose.yml builds the image locally:

# Set POSTGRES_PASSWORD in .env or export it
docker compose up -d

Database Extensions

The required PostgreSQL extensions are created automatically on first container start:

  • uuid-ossp -- UUID generation
  • citext -- case-insensitive text type
  • pg_trgm -- trigram matching for search

The production compose embeds the extension SQL inline using Docker Compose configs. The source build compose mounts backend/scripts/init-extensions.sql instead.

If you are using a managed PostgreSQL service, you must create these extensions manually. See the Railway guide for an example.

Production Environment Variables

At minimum, set the following variables for a production deployment:

VariableValueDescription
DATABASE_URLpostgres://user:pass@host:5432/dbPostgreSQL connection string
REDIS_URLredis://host:6379Redis connection string
APP__ENVIRONMENTproductionEnables production behavior
APP__HOST0.0.0.0Bind to all interfaces
APP__PORT8000Application port
ROCKET_ADDRESS0.0.0.0Rocket framework bind address
ROCKET_PORT8000Rocket framework port
APP__CORS_ORIGINShttps://yourdomain.comAllowed CORS origins (comma-separated)

For the full list of environment variables, see Environment Variables.

Health Checks

The application exposes a health endpoint at /health that returns the status of PostgreSQL and Redis connections:

curl http://localhost:8000/health
{
"status": "healthy",
"postgres": "connected",
"redis": "connected"
}

Use this endpoint in your Docker health check or load balancer configuration:

healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s

Verifying the Deployment

After starting the container, verify the following endpoints:

URLExpected Result
http://localhost:8000/healthJSON health status
http://localhost:8000/api-docsSwagger UI
http://localhost:8000/dashboardAdmin dashboard

Migrations

SQLx database migrations run automatically when the application starts. The migration files are bundled into the Docker image from backend/migrations/. No manual migration step is required.

Updating

To update a running deployment using the Docker Hub image:

docker pull dominikdorfstetter/forja
docker compose up -d

Or if building from source:

git pull
docker build -t forja .
docker compose up -d

Migrations are applied automatically on startup, so schema changes are handled without manual intervention.

Backup and Restore

Database Backup

Create a PostgreSQL dump while the stack is running:

docker compose -f docker-compose.prod.yml exec postgres \
pg_dump -U forja -d forja --format=custom -f /tmp/forja.dump

docker compose -f docker-compose.prod.yml cp postgres:/tmp/forja.dump ./forja-backup.dump

Database Restore

Restore from a backup file:

docker compose -f docker-compose.prod.yml cp ./forja-backup.dump postgres:/tmp/forja.dump

docker compose -f docker-compose.prod.yml exec postgres \
pg_restore -U forja -d forja --clean --if-exists /tmp/forja.dump

Upload Files Backup

The uploads volume stores media files. Back it up with:

docker run --rm \
-v forja_uploads:/data \
-v "$(pwd)":/backup \
alpine tar czf /backup/forja-uploads.tar.gz -C /data .

Restore:

docker run --rm \
-v forja_uploads:/data \
-v "$(pwd)":/backup \
alpine sh -c "cd /data && tar xzf /backup/forja-uploads.tar.gz"

Automated Backups

For scheduled backups, add a cron job on the host:

# Daily database backup at 2:00 AM, keep last 7 days
0 2 * * * cd /path/to/forja && docker compose -f docker-compose.prod.yml exec -T postgres pg_dump -U forja -d forja --format=custom > backups/forja-$(date +\%Y\%m\%d).dump && find backups/ -name "forja-*.dump" -mtime +7 -delete