Skip to main content

Polyrepo Configuration

Panopticon supports projects with multiple git repositories (like separate frontend/backend repos). Configure workspace settings directly in projects.yaml to manage polyrepo workspaces.

Overview

Polyrepo workspaces create git worktrees in each repository, allowing agents to work across multiple codebases simultaneously while maintaining proper git isolation. For polyrepo projects, we strongly recommend maintaining a dedicated infrastructure repository alongside your code repos:
myproject/
├── infra/                    # Infrastructure repo (git)
│   ├── .devcontainer-template/   # Templates for workspace creation
│   │   ├── dev.template          # Dev script template
│   │   ├── compose.infra.yml.template
│   │   └── docker-compose.devcontainer.yml.template
│   ├── new-feature              # Script to create workspaces
│   ├── remove-feature           # Script to clean up workspaces
│   ├── certs/                   # SSL certificates
│   └── traefik/                 # Traefik config
├── frontend/                 # Frontend repo (git)
├── backend/                  # Backend repo (git)
└── workspaces/               # Feature workspaces (git worktrees)
    └── feature-xxx/
        ├── fe/               # Worktree of frontend
        ├── api/              # Worktree of backend
        └── .devcontainer/    # Generated from templates
Benefits of a separate infra repo:
  1. Version-controlled templates - Dev script changes are tracked and shared
  2. Centralized configuration - DNS, ports, Traefik rules in one place
  3. Workspace consistency - All workspaces use the same templates
  4. Team collaboration - Infra changes go through normal PR review
  5. Easy updates - Fix a template once, regenerate affected workspaces
Key files in the infra repo:
FilePurpose
dev.templateTemplate for ./dev script (container management)
new-featureCreates workspace with git worktrees + devcontainer
remove-featureCleans up workspace and worktrees
.devcontainer-template/Docker Compose and devcontainer templates

Configuration in projects.yaml

projects:
  myapp:
    name: "My App"
    path: /home/user/projects/myapp
    linear_team: APP

    workspace:
      type: polyrepo
      workspaces_dir: workspaces
      # Default branch for all repos (optional, defaults to 'main')
      default_branch: main

      # Git repositories to include in each workspace
      # Each repo is a SEPARATE git repository with its own .git
      repos:
        - name: fe
          path: frontend           # Relative to project root
          branch_prefix: "feature/"
          # default_branch: main   # Can override workspace default per-repo
        - name: api
          path: backend
          branch_prefix: "feature/"
          # default_branch: develop  # Example: API uses 'develop' as default

      # DNS entries for local development
      dns:
        domain: myapp.test
        entries:
          - "{{FEATURE_FOLDER}}.{{DOMAIN}}"
          - "api-{{FEATURE_FOLDER}}.{{DOMAIN}}"
        sync_method: wsl2hosts  # or: hosts_file, dnsmasq

      # Port assignments for services
      ports:
        redis:
          range: [6380, 6499]

      # Service definitions - how to start each service
      services:
        - name: api
          path: api
          start_command: ./mvnw spring-boot:run
          docker_command: ./mvnw spring-boot:run
          health_url: "https://api-{{FEATURE_FOLDER}}.{{DOMAIN}}/actuator/health"
          port: 8080
        - name: frontend
          path: fe
          start_command: pnpm start
          docker_command: pnpm start
          health_url: "https://{{FEATURE_FOLDER}}.{{DOMAIN}}"
          port: 3000

      # Docker configuration
      docker:
        traefik: infra/docker-compose.traefik.yml
        compose_template: infra/.devcontainer-template

      # Agent configuration templates
      agent:
        template_dir: infra/.agent-template
        templates:
          - source: CLAUDE.md.template
            target: CLAUDE.md
        symlinks:
          - .claude/commands
          - .claude/skills

      # Environment template
      env:
        template: |
          COMPOSE_PROJECT_NAME={{COMPOSE_PROJECT}}
          FRONTEND_URL=https://{{FEATURE_FOLDER}}.{{DOMAIN}}

Template Placeholders

PlaceholderExampleDescription
{{FEATURE_NAME}}min-123Normalized issue ID
{{FEATURE_FOLDER}}feature-min-123Workspace folder name
{{BRANCH_NAME}}feature/min-123Git branch name
{{COMPOSE_PROJECT}}myapp-feature-min-123Docker Compose project
{{DOMAIN}}myapp.testDNS domain

Service Templates

Panopticon provides built-in templates for common frameworks. Use these to avoid boilerplate:
TemplateStart CommandPort
reactnpm start3000
react-vitenpm run dev5173
react-pnpmpnpm start3000
nextjsnpm run dev3000
spring-boot-maven./mvnw spring-boot:run8080
spring-boot-gradle./gradlew bootRun8080
expressnpm start3000
fastapiuvicorn main:app --reload8000
djangopython manage.py runserver8000
Use a template by referencing it in your service config:
services:
  - name: api
    template: spring-boot-maven
    path: api
    health_url: "https://api-{{FEATURE_FOLDER}}.myapp.test/health"
See /pan-workspace-config skill for complete documentation.

What Your Project Needs to Provide

Panopticon is an orchestration layer - it manages workspaces, agents, and workflows, but your project repository provides the actual templates and configuration. Projects can be as simple as just a git repo (for worktree-only workspaces) or as complex as a full polyrepo with Docker, Traefik, and database seeding.

Required: Workspace Templates

Your project needs a .devcontainer/ or template directory with:
your-project/
├── infra/
│   └── .devcontainer-template/      # Template for workspace containers
│       ├── docker-compose.devcontainer.yml.template
│       ├── compose.infra.yml.template   # Optional: separate infra services
│       ├── Dockerfile
│       └── devcontainer.json.template
└── ...
Docker Compose templates should use placeholders that Panopticon will replace:
# docker-compose.devcontainer.yml.template
services:
  api:
    build: ./api
    user: vscode  # Run as non-root to avoid permission issues
    labels:
      - "traefik.http.routers.{{FEATURE_FOLDER}}-api.rule=Host(`api-{{FEATURE_FOLDER}}.{{DOMAIN}}`)"
    environment:
      - DATABASE_URL=postgres://app:app@postgres:5432/mydb

  frontend:
    build: ./fe
    user: vscode  # Run as non-root to avoid permission issues
    labels:
      - "traefik.http.routers.{{FEATURE_FOLDER}}.rule=Host(`{{FEATURE_FOLDER}}.{{DOMAIN}}`)"
⚠️ Important: File Permissions Always run containers as a non-root user (e.g., user: vscode) to avoid permission issues. Files created by root-owned containers cannot be removed by pan workspace destroy without sudo. The Microsoft devcontainers base image includes a vscode user (UID 1000) that matches most host users.

Required for HTTPS: Traefik Configuration

If you want local HTTPS (recommended), provide a Traefik compose file:
your-project/
├── infra/
│   └── docker-compose.traefik.yml   # Traefik reverse proxy
└── ...
Example Traefik config:
# infra/docker-compose.traefik.yml
services:
  traefik:
    image: traefik:v2.10
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ~/.panopticon/traefik/certs:/certs:ro
    command:
      - --providers.docker=true
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443

Required for Database Seeding: Seed Directory

For projects with databases:
your-project/
├── infra/
│   └── seed/
│       └── seed.sql              # Sanitized production data
└── ...
Your compose template should mount this:
services:
  postgres:
    image: postgres:16
    volumes:
      - /path/to/project/infra/seed:/docker-entrypoint-initdb.d:ro

Optional: Agent Templates

For customizing how agents work in your project:
your-project/
├── infra/
│   └── .agent-template/
│       ├── CLAUDE.md.template    # Project-specific AI instructions
│       └── .mcp.json.template    # MCP server configuration
└── .claude/
    └── skills/                   # Project-specific skills
        └── my-project-standards/
            └── SKILL.md

Quick Checklist

ComponentRequired?LocationPurpose
Docker Compose templateYes (for Docker workspaces)infra/.devcontainer-template/Container configuration
Traefik configOnly for HTTPSinfra/docker-compose.traefik.ymlReverse proxy
Seed fileOnly if database neededinfra/seed/seed.sqlPre-populate database
Agent templateRecommendedinfra/.agent-template/AI instructions
Project skillsOptional.claude/skills/Project-specific workflows

Example: Minimal Setup

For a simple monorepo with no Docker:
# ~/.panopticon/projects.yaml
projects:
  simple-app:
    name: "Simple App"
    path: /home/user/projects/simple-app
    linear_team: APP
    # No workspace config needed - uses git worktrees
Panopticon creates workspaces as git worktrees. Docker, HTTPS, and seeding are opt-in.

Container Configuration Tips

When setting up Docker containers for workspaces, avoid these common pitfalls: Maven projects:
  • DO NOT set MAVEN_CONFIG=/some/path as an environment variable
  • Maven interprets MAVEN_CONFIG as additional CLI arguments, not a directory path
  • This causes “Unknown lifecycle phase” errors (e.g., “Unknown lifecycle phase /maven-cache”)
  • Instead, use -Dmaven.repo.local=/path/to/cache in the Maven command
# WRONG - causes Maven startup failure
environment:
  - MAVEN_CONFIG=/maven-cache

# CORRECT - use command line argument
command: ./mvnw spring-boot:run -Dmaven.repo.local=/maven-cache/repository
volumes:
  - ~/.m2:/maven-cache:cached
pnpm projects:
  • Set PNPM_HOME=/path to configure the pnpm store location
  • Mount a named volume for the store to share across containers

Polyrepo Merge Considerations

⚠️ Important: Polyrepo merging requires special handling. The current merge-agent is optimized for monorepos.
For polyrepo projects:
FeatureStatusNotes
Workspace creation✅ SupportedCreates worktrees in each repo
Branch management✅ SupportedEach repo gets its own feature branch
Agent work✅ SupportedAgents can work across repos
Merge⚠️ ManualPush each repo’s branch, merge via GitLab/GitHub
Current workflow for polyrepo merges:
  1. Agent completes work and pushes branches to each repo
  2. Create merge requests for each repo manually (or via gh pr create)
  3. Review and merge each MR separately
  4. The “Approve & Merge” button is not yet polyrepo-aware
Future enhancement: Polyrepo-aware merge-agent that handles multiple repos automatically.

Quality Gates in Polyrepo Projects

Quality gates support polyrepo projects through path-based filtering: each gate runs only when the repo being merged matches the gate’s path field.

How it works

When a polyrepo merge runs, Panopticon computes the relative path from the project root to the sub-repo being merged (e.g., frontend or backend). It then filters the configured quality gates to only those whose path field matches that relative path.
Gate configRepo being mergedResult
path: frontendfrontend✅ Runs
path: backendfrontend⏭️ Skipped
no path fieldfrontend⏭️ Skipped (not applicable to any specific repo)
no path fieldproject root (monorepo)✅ Runs

Configuration example

projects:
  myapp:
    name: "My App"
    path: /home/user/projects/myapp

    quality_gates:
      # Runs only when merging the frontend repo
      fe-lint:
        command: pnpm lint
        path: frontend
        required: true
        phase: pre_push

      # Runs only when merging the backend repo
      api-checkstyle:
        command: ./mvnw checkstyle:check
        path: backend
        required: true
        phase: pre_push

      # Global gate — runs in monorepo context only (no path = skipped in polyrepo)
      integration-tests:
        command: make integration-test
        required: false
        phase: post_push

    workspace:
      type: polyrepo
      repos:
        - name: fe
          path: frontend       # Must match gate.path exactly
          branch_prefix: "feature/"
        - name: api
          path: backend        # Must match gate.path exactly
          branch_prefix: "feature/"
Important: The path value in a quality gate must exactly match the path value of the corresponding repo in the workspace.repos list (both relative to the project root).