Skip to main content
The project uses Docker Compose to provide local infrastructure for development. Two services are defined: a PostgreSQL database and a Mailpit SMTP server for local email testing.

Services

db

PostgreSQL 16 (Alpine). Stores all fleet management data. Persisted via the postgres_data named volume.

mailpit

Local SMTP server and web UI for catching outgoing emails during development. No emails are delivered externally.

Port mappings

ServicePortPurpose
db5432PostgreSQL connections
mailpit1025SMTP (send emails to)
mailpit8025Mailpit web UI
Open http://localhost:8025 in your browser to inspect captured emails.

Starting services

1

Start the database

docker-compose up -d db
This starts PostgreSQL with the postgres_data volume mounted at /var/lib/postgresql/data. Data persists across container restarts.
2

Start the mail server

docker-compose up -d mailpit
Point your backend SMTP_HOST to 127.0.0.1 and SMTP_PORT to 1025.
3

Start all services at once

docker-compose up -d
Starts both db and mailpit in the background.

docker-compose.yml

version: "3.9"

services:
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: fleet_management
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

  mailpit:
    image: axllent/mailpit:latest
    restart: unless-stopped
    ports:
      - "1025:1025"
      - "8025:8025"

volumes:
  postgres_data:

Backend Dockerfile

The backend image is built from packages/backend/Dockerfile. Here is what each stage does:
FROM node:20-alpine

WORKDIR /app

# Copy manifests first to leverage Docker layer caching
COPY package.json yarn.lock ./
COPY packages/backend/package.json packages/backend/package.json
COPY shipfree/core/package.json shipfree/core/package.json

# Install all workspace dependencies
RUN yarn install --frozen-lockfile || yarn install

# Copy application source
COPY packages/backend packages/backend
COPY shipfree/core shipfree/core

WORKDIR /app/packages/backend

# Create the uploads directory for file storage
RUN mkdir -p uploads

EXPOSE 3001

# On startup: run pending Prisma migrations, then start the server
CMD ["sh", "-lc", "npx prisma migrate deploy && yarn start"]
The image:
  • Uses Node 20 on Alpine Linux for a small footprint.
  • Copies package.json manifests before source code so dependencies are cached unless lockfiles change.
  • Includes the shipfree/core shared package alongside the backend.
  • Creates an uploads directory for user-uploaded files (mounted as a bind volume in production).
  • Runs prisma migrate deploy automatically on every container start before launching the server.
  • Exposes port 3001.

Environment variables for Docker-based setup

Set these in packages/backend/.env when running against the Docker Compose database and Mailpit:
PORT=3001
NODE_ENV=development
DB_SYNC=false
DB_SEED=false
DATABASE_URL=postgresql://postgres:[email protected]:5432/fleet_management?schema=public
DB_HOST=127.0.0.1
DB_PORT=5432
DB_NAME=fleet_management
DB_USER=postgres
DB_PASSWORD=postgres
JWT_SECRET=change-me
DEFAULT_LOGIN_PASSWORD=changeme123
SMTP_HOST=127.0.0.1
SMTP_PORT=1025
SMTP_SECURE=false
SMTP_USER=
SMTP_PASS=
EMAIL_FROM=[email protected]
The default PostgreSQL credentials (postgres / postgres) are intended for local development only. Never use these credentials in staging or production.

Volumes

The postgres_data named volume persists your database across docker-compose down and container restarts. To fully reset local data, remove the volume explicitly:
docker-compose down -v

Build docs developers (and LLMs) love