Skip to content

Local Development & Demo Strategy

Purpose

This document defines the local development experience and "demo in a box" strategy for the Farmer1st platform, enabling developers and sales teams to run the complete stack locally.

Current State

Goals

Audience Goal
Developers Full stack running locally with hot reload for rapid iteration
AI assistants Complete context to make changes across apps, services, and infra
Sales team One command to spin up a demo with realistic data
Product team Prototype new features without deploying to cloud

One-Command Experience

# Developer starting work
git clone git@github.com:farmer1st/farmer1st.git
cd farmer1st
make dev

# Sales preparing for demo
make demo scenario=cocoa-cooperative

# Both result in:
# - Full stack running in Docker
# - Hot reload for code changes
# - Seeded with appropriate data

Architecture: Local Stack

┌─────────────────────────────────────────────────────────────────────────────┐
│                         LOCAL DEVELOPMENT STACK                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   BROWSER                                                                   │
│   ┌─────────────────────────────┐   ┌─────────────────────────────┐        │
│   │ http://localhost:3000       │   │ http://localhost:3001       │        │
│   │ Farmer PWA                  │   │ Stakeholder Portal          │        │
│   └─────────────────────────────┘   └─────────────────────────────┘        │
│                                                                             │
│   DOCKER COMPOSE                                                            │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │                                                                      │  │
│   │   APPS (Hot Reload)                                                  │  │
│   │   ┌──────────────┐    ┌──────────────┐                              │  │
│   │   │ farmer-pwa   │    │ stakeholder- │                              │  │
│   │   │ :3000        │    │ portal :3001 │                              │  │
│   │   └──────────────┘    └──────────────┘                              │  │
│   │                                                                      │  │
│   │   BFFs (Hot Reload)                                                  │  │
│   │   ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐│  │
│   │   │ user-bff     │ │relationship- │ │ surveys-bff  │ │payments-bff││  │
│   │   │ :4001        │ │ bff :4002    │ │ :4003        │ │ :4004      ││  │
│   │   └──────────────┘ └──────────────┘ └──────────────┘ └────────────┘│  │
│   │                                                                      │  │
│   │   SERVICES (Hot Reload)                                              │  │
│   │   ┌──────────────┐ ┌──────────────┐ ┌──────────────┐               │  │
│   │   │ auth-service │ │profile-svc   │ │ invite-svc   │  ...          │  │
│   │   │ :5001        │ │ :5002        │ │ :5003        │               │  │
│   │   └──────────────┘ └──────────────┘ └──────────────┘               │  │
│   │                                                                      │  │
│   │   PLATFORM SERVICES                                                  │  │
│   │   ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐│  │
│   │   │ SuperTokens  │ │ OpenFGA      │ │ Temporal     │ │ Unleash    ││  │
│   │   │ :3567        │ │ :8080        │ │ :7233        │ │ :4242      ││  │
│   │   └──────────────┘ └──────────────┘ └──────────────┘ └────────────┘│  │
│   │                                                                      │  │
│   │   DATA STORES                                                        │  │
│   │   ┌──────────────┐ ┌──────────────┐ ┌──────────────┐               │  │
│   │   │ PostgreSQL   │ │ Redis        │ │ Kafka        │               │  │
│   │   │ :5432        │ │ :6379        │ │ :9092        │               │  │
│   │   └──────────────┘ └──────────────┘ └──────────────┘               │  │
│   │                                                                      │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Docker Compose Configuration

Main docker-compose.yml

# docker-compose.yml
version: '3.8'

services:
  # ============================================
  # FRONTEND APPS
  # ============================================
  farmer-pwa:
    build:
      context: ./apps/farmer-pwa
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - ./apps/farmer-pwa/src:/app/src
      - ./packages:/packages
    environment:
      - VITE_API_URL=http://localhost:4001/graphql
      - VITE_AUTH_URL=http://localhost:3567
    depends_on:
      - user-bff

  stakeholder-portal:
    build:
      context: ./apps/stakeholder-portal
      dockerfile: Dockerfile.dev
    ports:
      - "3001:3001"
    volumes:
      - ./apps/stakeholder-portal/src:/app/src
      - ./packages:/packages
    environment:
      - VITE_API_URL=http://localhost:4001/graphql
      - VITE_AUTH_URL=http://localhost:3567
    depends_on:
      - user-bff

  # ============================================
  # BFFs (GraphQL)
  # ============================================
  user-bff:
    build:
      context: ./services/user-management/bff
      dockerfile: Dockerfile.dev
    ports:
      - "4001:4001"
    volumes:
      - ./services/user-management/bff/src:/app/src
      - ./packages:/packages
    environment:
      - AUTH_SERVICE_URL=http://auth-service:5001
      - PROFILE_SERVICE_URL=http://profile-service:5002
      - SUPERTOKENS_URL=http://supertokens:3567
    depends_on:
      - auth-service
      - profile-service
      - supertokens

  # ... (other BFFs follow same pattern)

  # ============================================
  # MICROSERVICES
  # ============================================
  auth-service:
    build:
      context: ./services/user-management/auth-service
      dockerfile: Dockerfile.dev
    ports:
      - "5001:5001"
    volumes:
      - ./services/user-management/auth-service/src:/app/src
    environment:
      - DATABASE_URL=postgresql://farmer1st:farmer1st@postgres:5432/farmer1st
      - SUPERTOKENS_URL=http://supertokens:3567
    depends_on:
      - postgres
      - supertokens

  profile-service:
    build:
      context: ./services/user-management/profile-service
      dockerfile: Dockerfile.dev
    ports:
      - "5002:5002"
    volumes:
      - ./services/user-management/profile-service/src:/app/src
    environment:
      - DATABASE_URL=postgresql://farmer1st:farmer1st@postgres:5432/farmer1st
      - REDIS_URL=redis://redis:6379
    depends_on:
      - postgres
      - redis

  # ... (other services follow same pattern)

  # ============================================
  # PLATFORM SERVICES
  # ============================================
  supertokens:
    image: supertokens/supertokens-postgresql:latest
    ports:
      - "3567:3567"
    environment:
      - POSTGRESQL_CONNECTION_URI=postgresql://farmer1st:farmer1st@postgres:5432/supertokens
    depends_on:
      - postgres

  openfga:
    image: openfga/openfga:latest
    ports:
      - "8080:8080"
      - "8081:8081"  # gRPC
    command: run
    environment:
      - OPENFGA_DATASTORE_ENGINE=postgres
      - OPENFGA_DATASTORE_URI=postgresql://farmer1st:farmer1st@postgres:5432/openfga
    depends_on:
      - postgres

  temporal:
    image: temporalio/auto-setup:latest
    ports:
      - "7233:7233"
    environment:
      - DB=postgresql
      - DB_PORT=5432
      - POSTGRES_USER=farmer1st
      - POSTGRES_PWD=farmer1st
      - POSTGRES_SEEDS=postgres
    depends_on:
      - postgres

  temporal-ui:
    image: temporalio/ui:latest
    ports:
      - "8088:8080"
    environment:
      - TEMPORAL_ADDRESS=temporal:7233

  unleash:
    image: unleashorg/unleash-server:latest
    ports:
      - "4242:4242"
    environment:
      - DATABASE_URL=postgresql://farmer1st:farmer1st@postgres:5432/unleash
      - DATABASE_SSL=false
    depends_on:
      - postgres

  # ============================================
  # DATA STORES
  # ============================================
  postgres:
    image: postgres:15
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=farmer1st
      - POSTGRES_PASSWORD=farmer1st
      - POSTGRES_DB=farmer1st
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./infra/docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  kafka:
    image: bitnami/kafka:latest
    ports:
      - "9092:9092"
    environment:
      - KAFKA_CFG_NODE_ID=0
      - KAFKA_CFG_PROCESS_ROLES=controller,broker
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka:9093
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
    volumes:
      - kafka_data:/bitnami/kafka

volumes:
  postgres_data:
  redis_data:
  kafka_data:

Demo Profile

# docker-compose.demo.yml
# Extends docker-compose.yml with demo-specific settings

version: '3.8'

services:
  farmer-pwa:
    environment:
      - VITE_DEMO_MODE=true
      - VITE_AUTO_LOGIN=true

  stakeholder-portal:
    environment:
      - VITE_DEMO_MODE=true
      - VITE_AUTO_LOGIN=true

  # Seed service runs once to populate demo data
  seeder:
    build:
      context: ./tools/seed-data
    environment:
      - DATABASE_URL=postgresql://farmer1st:farmer1st@postgres:5432/farmer1st
      - OPENFGA_URL=http://openfga:8080
      - SCENARIO=${DEMO_SCENARIO:-cocoa-cooperative}
    depends_on:
      - postgres
      - openfga
      - supertokens

Makefile Commands

# Makefile

.PHONY: dev demo test clean help

# Development with hot reload
dev:
    docker compose up --build

# Development (detached)
dev-d:
    docker compose up --build -d

# Stop all services
stop:
    docker compose down

# Demo mode with seeded data
demo:
    DEMO_SCENARIO=$(scenario) docker compose -f docker-compose.yml -f docker-compose.demo.yml up --build

# Available scenarios: cocoa-cooperative, coffee-brand, small-farm
demo-cocoa:
    $(MAKE) demo scenario=cocoa-cooperative

demo-coffee:
    $(MAKE) demo scenario=coffee-brand

demo-small:
    $(MAKE) demo scenario=small-farm

# Reset all data
reset:
    docker compose down -v
    docker compose up --build

# Run tests
test:
    docker compose run --rm test

# Run specific service tests
test-service:
    docker compose run --rm $(service) pytest

# Generate API clients from OpenAPI specs
generate-clients:
    ./tools/scripts/generate-clients.sh

# Seed data (without full demo mode)
seed:
    docker compose run --rm seeder python seed.py --scenario $(scenario)

# View logs
logs:
    docker compose logs -f $(service)

# Shell into a service
shell:
    docker compose exec $(service) /bin/sh

# Clean everything
clean:
    docker compose down -v --rmi local
    docker system prune -f

# Help
help:
    @echo "Available commands:"
    @echo "  make dev              - Start development environment"
    @echo "  make demo             - Start demo with default scenario"
    @echo "  make demo-cocoa       - Demo: West Africa cocoa cooperative"
    @echo "  make demo-coffee      - Demo: Coffee brand (Nestlé scenario)"
    @echo "  make demo-small       - Demo: Single small farm"
    @echo "  make stop             - Stop all services"
    @echo "  make reset            - Reset all data and restart"
    @echo "  make test             - Run all tests"
    @echo "  make logs service=x   - View logs for service x"
    @echo "  make shell service=x  - Shell into service x"
    @echo "  make clean            - Remove all containers and images"

Demo Scenarios

Scenario: Cocoa Cooperative

tools/seed-data/scenarios/cocoa-cooperative/
├── manifest.json           # Scenario metadata
├── users.json              # 50 farmers, 5 coop admins, 2 agents
├── farms.json              # Farms with cocoa plots in Ghana/Côte d'Ivoire
├── cooperatives.json       # "West Africa Cocoa Collective"
├── brands.json             # Mars, Nestlé as buyers
├── relationships.json      # OpenFGA tuples
├── surveys.json            # Completed sustainability surveys
└── demo-credentials.json   # Pre-configured login credentials

Demo credentials (cocoa-cooperative): | Role | Email | Password | What they see | |------|-------|----------|---------------| | Farmer | demo-farmer@farmer1st.local | demo123 | Their farm, surveys, points | | Coop Admin | demo-coop@farmer1st.local | demo123 | All coop farmers, aggregated data | | Brand Employee | demo-brand@farmer1st.local | demo123 | Supply chain, sourcing data |

Scenario: Coffee Brand

Similar structure, but focused on coffee supply chain with different geography (Colombia, Ethiopia).

Scenario: Small Farm

Minimal setup for quick demos - single farmer, single farm, basic surveys.

Resource Requirements

Minimum (Lite Mode - TBD)

Resource Requirement
RAM 8GB
CPU 4 cores
Disk 10GB

Lite mode would mock heavy services (Kafka, Temporal) for resource-constrained laptops.

Resource Requirement
RAM 16GB
CPU 8 cores
Disk 20GB

What Each Service Uses

Service RAM (approx) Notes
PostgreSQL 512MB Shared by all
Redis 128MB
Kafka 1GB Can be mocked for lite mode
Temporal 512MB Can be mocked for lite mode
SuperTokens 256MB
OpenFGA 256MB
Unleash 256MB
Each Python service 128-256MB ~10 services
Each React app 256MB 2 apps (dev server)

Total: ~6-8GB for full stack

Hot Reload Setup

Frontend (Vite)

React apps use Vite with hot module replacement:

# apps/farmer-pwa/Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

Backend (Python)

Python services use uvicorn with reload:

# services/user-management/auth-service/Dockerfile.dev
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml ./
RUN pip install -e ".[dev]"
COPY . .
EXPOSE 5001
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "5001", "--reload"]

Troubleshooting

Common Issues

Issue Solution
Port already in use make stop then make dev
Database connection refused Wait for postgres to be ready, or make reset
Out of disk space make clean to remove unused images
Service won't start Check logs: make logs service=<name>
Stale data make reset to clear volumes

Health Checks

# Check all services are healthy
docker compose ps

# Check specific service logs
make logs service=user-bff

# Check database connectivity
docker compose exec postgres psql -U farmer1st -c "SELECT 1"

# Check OpenFGA
curl http://localhost:8080/healthz

Open Questions

  • Lite mode implementation (mock Kafka/Temporal)?
  • Pre-built demo images for faster startup?
  • Demo data refresh strategy?
  • Offline demo capability (airplane mode)?
  • Windows/WSL2 compatibility testing?

Dependencies

  • Docker & Docker Compose
  • Make (or equivalent task runner)
  • ~16GB RAM for full stack
  • Git for cloning mono-repo

Last Updated: 2025-12-30