Skip to main content

Overview

Trazea includes Docker configuration for both development and production deployments. The multi-stage Dockerfile optimizes for fast development with hot-reload and efficient production builds with Nginx.

Prerequisites

  • Docker installed (version 20.10+)
  • Docker Compose installed (version 2.0+)
  • .env file with required environment variables

Quick Start

Development Mode

Run Trazea in development mode with hot-reload:
# Clone the repository
git clone <repository-url>
cd trazea

# Copy environment variables
cp .env.example .env
# Edit .env with your Supabase credentials

# Start development container
docker-compose up --build
The application will be available at http://localhost:5173

Production Mode

Build and run the production container:
# Build production image
docker build -t trazea:latest \
  --build-arg PUBLIC_SUPABASE_URL=https://your-project.supabase.co \
  --build-arg PUBLIC_SUPABASE_ANON_TOKEN=your-anon-key \
  .

# Run production container
docker run -p 80:80 trazea:latest
The application will be available at http://localhost

Dockerfile Architecture

Trazea uses a multi-stage Dockerfile with three stages:

Stage 1: Development Base (dev)

FROM node:22-alpine AS dev
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install
Purpose: Creates the base image with all dependencies installed.
  • Uses Node 22 Alpine for minimal image size
  • Enables pnpm via corepack
  • Installs all dependencies (including devDependencies)

Stage 2: Builder (builder)

FROM dev AS builder
ARG PUBLIC_SUPABASE_URL
ARG PUBLIC_SUPABASE_ANON_TOKEN
COPY . .
RUN VITE_SUPABASE_URL=$PUBLIC_SUPABASE_URL \
    VITE_SUPABASE_ANON_KEY=$PUBLIC_SUPABASE_ANON_TOKEN \
    pnpm build
Purpose: Builds the production bundle.
  • Accepts build-time arguments for Supabase configuration
  • Runs pnpm build with environment variables
  • Outputs optimized static files to /app/dist
Environment variables are baked into the build at compile time. To change them, you must rebuild the image.

Stage 3: Production Runner (runner)

FROM nginx:alpine AS runner
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Purpose: Serves the built application via Nginx.
  • Uses Nginx Alpine for minimal runtime image (~25 MB)
  • Copies only production files from builder stage
  • Exposes port 80 for HTTP traffic
  • Runs Nginx in foreground mode

Docker Compose Configuration

The docker-compose.yml file is optimized for local development:
docker-compose.yml
services:
  app:
    build:
      context: .
      target: dev          # Use 'dev' stage for hot-reload
      args:
        PUBLIC_SUPABASE_URL: ${VITE_SUPABASE_URL}
        PUBLIC_SUPABASE_ANON_TOKEN: ${VITE_SUPABASE_ANON_KEY}
    ports:
      - "5173:5173"
    container_name: minca-inventory
    env_file:
      - .env               # Load all environment variables
    volumes:
      - .:/app             # Sync source code for hot-reload
      - /app/node_modules  # Protect node_modules in container
    environment:
      - VITE_WATCH_USE_POLLING=true  # Enable polling for Windows/Mac
    command: pnpm run dev -- --host 0.0.0.0 --port 5173

Key Features

Source code is mounted as a volume (.:/app), so changes are instantly reflected without rebuilding.The VITE_WATCH_USE_POLLING=true environment variable enables file watching on Windows and macOS hosts.
The /app/node_modules volume ensures that the container’s installed dependencies aren’t overwritten by the host’s node_modules folder.
The env_file: - .env directive loads all variables from your .env file into the container.

Deployment Scenarios

Local Development

# Start with auto-rebuild on Dockerfile changes
docker-compose up --build
Access the application at http://localhost:5173

Production Deployment

1

Build Production Image

docker build -t trazea:1.0.0 \
  --build-arg PUBLIC_SUPABASE_URL=https://prod.supabase.co \
  --build-arg PUBLIC_SUPABASE_ANON_TOKEN=prod-anon-key \
  --target runner \
  .
2

Test Locally

docker run -p 8080:80 --name trazea-test trazea:1.0.0
Visit http://localhost:8080 to test the production build.
3

Tag for Registry

docker tag trazea:1.0.0 your-registry.com/trazea:1.0.0
docker tag trazea:1.0.0 your-registry.com/trazea:latest
4

Push to Registry

docker push your-registry.com/trazea:1.0.0
docker push your-registry.com/trazea:latest
5

Deploy to Server

# On your production server
docker pull your-registry.com/trazea:latest
docker run -d \
  --name trazea-prod \
  -p 80:80 \
  --restart unless-stopped \
  your-registry.com/trazea:latest

Docker Swarm / Kubernetes

For orchestrated deployments, use the production image with config management:
version: '3.8'
services:
  trazea:
    image: your-registry.com/trazea:latest
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
    ports:
      - "80:80"
    networks:
      - trazea-network

networks:
  trazea-network:
    driver: overlay

Environment Variables in Docker

Build-Time Variables

Passed as --build-arg during docker build:
docker build \
  --build-arg PUBLIC_SUPABASE_URL=https://your-project.supabase.co \
  --build-arg PUBLIC_SUPABASE_ANON_TOKEN=your-anon-key \
  -t trazea:latest .
Build-time variables are baked into the JavaScript bundle. To change them, you must rebuild the image.

Runtime Variables (Development Only)

In development mode with docker-compose, variables are loaded from .env:
env_file:
  - .env  # Loads VITE_* variables at runtime

Nginx Configuration

The production image uses the default Nginx Alpine configuration, which automatically:
  • Serves files from /usr/share/nginx/html
  • Handles SPA routing with proper fallback to index.html
  • Compresses assets with gzip
  • Sets cache headers for static files
For custom Nginx configuration, create nginx.conf:
nginx.conf
server {
    listen 80;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;

    # SPA routing: fallback to index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Cache static assets
    location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    add_header X-XSS-Protection "1; mode=block";

    # Gzip compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}
Then update the Dockerfile:
FROM nginx:alpine AS runner
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Troubleshooting

If port 5173 or 80 is already in use:
# Change port in docker-compose.yml
ports:
  - "8080:5173"  # Use 8080 instead
Or stop the conflicting container:
docker ps
docker stop <container-id>
  1. Ensure volume is mounted: -v .:/app
  2. Check file watching is enabled: VITE_WATCH_USE_POLLING=true
  3. Restart the container: docker-compose restart
Check build logs:
docker build --no-cache -t trazea:latest . 2>&1 | tee build.log
Common issues:
  • Missing build args: Add --build-arg PUBLIC_SUPABASE_URL=...
  • TypeScript errors: Run pnpm build locally first to identify issues
  • Out of memory: Increase Docker memory limit in settings
Check container logs:
docker logs <container-id>
For production containers, ensure Nginx is running:
docker exec -it <container-id> nginx -t

Image Size Optimization

The multi-stage build keeps images small:
StageSizeContents
dev~800 MBNode + source + all dependencies
builder~850 MBdev + built files
runner~25 MBNginx + built static files only
Only the runner stage is pushed to production, keeping deployment fast and efficient.

Production Checklist

1

Build Arguments

✓ Production Supabase URL and key configured as build args✓ Sentry DSN included in build (if used)
2

Image Security

✓ No secrets in build logs or layers✓ Base images up to date (node:22-alpine, nginx:alpine)✓ Image scanned for vulnerabilities
3

Deployment

✓ Container runs with --restart unless-stopped✓ Health checks configured (optional)✓ SSL/TLS termination at load balancer or reverse proxy
4

Monitoring

✓ Container logs forwarded to logging system✓ Sentry error tracking active✓ Resource limits set (CPU/memory)

Next Steps

Environment Setup

Configure environment variables and application settings

Supabase Configuration

Set up authentication, RLS policies, and CORS

Build docs developers (and LLMs) love