TL;DR:
- The Meeting Intelligence Platform uses Go (SSE service), Python (API, worker, Streamlit), and PHP (Drupal CMS) in a single repository.
- Each language has its own directory with its own dependency management, Dockerfile, and tooling—no cross-language build complexity.
- Docker Compose ties everything together for local development; each service is independently deployable in production.
- The key insight: polyglot doesn't mean complicated. Keep services isolated, share only via APIs and message queues.
When I designed the Meeting Intelligence Platform, I chose different languages for different jobs:
- Go for the SSE service — Low-memory, high-concurrency connection handling
- Python for the API and worker — Rich AI/ML ecosystem, rapid development
- PHP for Drupal — Client's existing CMS platform and team expertise
This isn't "polyglot for the sake of polyglot." Each language was chosen because it's the best tool for that specific job. The challenge is organizing the project so this multi-language setup doesn't become a maintenance nightmare.
For clients, this structure means we can safely introduce new services (like the Go SSE layer or a new AI worker) without destabilizing the existing Drupal site. Each piece is isolated, tested, and deployable on its own.
The Directory Structure
agile-creative-demos/
├── docker-compose.yml # Orchestrates all services
├── Makefile # Common commands
├── .env.example # Environment template
├── config/
│ └── keys/ # RSA keys for service auth
├── meeting-intelligence/ # Python + Go services
│ ├── api/ # FastAPI application
│ ├── worker/ # Celery tasks
│ ├── shared/ # Shared Python code
│ ├── streamlit/ # Standalone demo app
│ ├── sse/ # Go SSE service (separate module)
│ │ ├── go.mod
│ │ ├── Dockerfile
│ │ └── *.go
│ ├── tests/
│ ├── pyproject.toml # Python dependencies
│ └── Dockerfile # Python services
├── drupal/ # PHP CMS
│ ├── web/
│ │ └── modules/custom/ # Custom Drupal modules
│ ├── config/
│ ├── composer.json
│ └── Dockerfile
└── docs/
├── case-study/
└── blog/
Key principles:
- Each language ecosystem is self-contained — Python has
pyproject.toml, Go hasgo.mod, PHP hascomposer.json - Shared code stays within language boundaries — Python services share via
shared/, but nothing crosses language lines except HTTP/Redis - Each deployable unit has its own Dockerfile — No multi-stage builds mixing languages
Why Not Separate Repositories?
I considered splitting into three repos (Python services, Go service, Drupal). Here's why I kept it together:
Pros of monorepo:
- Single PR can update API schema and CMS integration together
- Shared CI/CD configuration
- Easier to keep documentation in sync
- One
docker-compose upstarts everything
Cons of monorepo:
- Larger clone size
- CI runs for all languages even when only one changed
- Teams working on different languages see each other's noise
For a solo developer or small team, the monorepo wins. The "single PR for cross-cutting changes" benefit alone is worth it. For larger teams with dedicated Go/Python/PHP specialists, separate repos might make sense.
The Go Service: Isolated Module
The SSE service is a Go module inside the Python project directory:
meeting-intelligence/
└── sse/
├── go.mod
├── go.sum
├── Dockerfile
├── main.go
├── server.go
├── hub.go
├── handlers.go
├── redis.go
└── config.go
The go.mod makes this a standalone Go module:
module github.com/agilecreativeminds/sse-service
go 1.22
require github.com/redis/go-redis/v9 v9.7.0
It has its own Dockerfile with a multi-stage build:
# Build stage
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod ./
COPY *.go ./
RUN go mod tidy && go mod download
RUN CGO_ENABLED=0 GOOS=linux go build -o sse-service .
# Runtime stage
FROM alpine:3.19
WORKDIR /app
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/sse-service .
EXPOSE 8080
CMD ["./sse-service"]
The Go service knows nothing about Python. It reads from Redis and serves HTTP. That's it.
The Python Services: Shared Codebase
Python handles multiple services from one codebase:
meeting-intelligence/
├── api/ # FastAPI app
│ ├── main.py
│ ├── routers/
│ ├── models.py
│ └── ...
├── worker/ # Celery worker
│ ├── celery_app.py
│ └── tasks.py
├── shared/ # Shared code
│ ├── services/
│ └── data/
├── streamlit/ # Demo UI
│ └── app.py
├── pyproject.toml
└── Dockerfile
All Python services share the same pyproject.toml and Dockerfile. Docker Compose runs different commands:
# API service
api:
build:
context: .
dockerfile: meeting-intelligence/Dockerfile
command: uv run uvicorn api.main:app --host 0.0.0.0 --port 8000
# Celery worker
worker:
build:
context: .
dockerfile: meeting-intelligence/Dockerfile
command: uv run celery -A worker.celery_app worker --loglevel=info
# Streamlit app
streamlit:
build:
context: .
dockerfile: meeting-intelligence/Dockerfile
command: uv run streamlit run streamlit/app.py --server.port=8501
Same image, different entrypoints. This simplifies builds and ensures all Python services have identical dependencies.
The Drupal Service: Standard CMS Structure
Drupal follows its standard structure with custom modules:
drupal/
├── web/
│ ├── modules/
│ │ └── custom/
│ │ └── meeting_intelligence/ # API integration
│ │ ├── src/
│ │ │ ├── Controller/
│ │ │ └── Service/
│ │ ├── meeting_intelligence.module
│ │ └── meeting_intelligence.info.yml
│ └── themes/
├── config/
│ └── sync/ # Exported config
├── composer.json
├── composer.lock
└── Dockerfile
The custom meeting_intelligence module contains all API integration code. Standard Drupal developers can work on theming and content without touching the integration layer.
Docker Compose: The Glue
Docker Compose orchestrates all services:
services:
# Reverse proxy
traefik:
image: traefik:v2.11
ports:
- "8091:80"
# Python API
api:
build:
context: .
dockerfile: meeting-intelligence/Dockerfile
depends_on:
- db
- redis
# Python Celery worker
worker:
build:
context: .
dockerfile: meeting-intelligence/Dockerfile
depends_on:
- db
- redis
# Go SSE service
sse:
build:
context: ./meeting-intelligence/sse
dockerfile: Dockerfile
depends_on:
- redis
# PHP Drupal
drupal:
build:
context: ./drupal
dockerfile: Dockerfile
depends_on:
- drupal-db
# Databases
db:
image: postgres:16-alpine
drupal-db:
image: mariadb:11.4
# Shared infrastructure
redis:
image: redis:7-alpine
Each service has its own build context pointing to the right Dockerfile. Services communicate via:
- HTTP — Drupal → API, browsers → all services
- Redis — Worker → SSE (pub/sub for job updates)
- PostgreSQL — API and Worker share the meetings database
- MariaDB — Drupal's own database
No direct code sharing across languages. All integration happens through network protocols.
Development Workflow
Starting everything:
docker compose up -d
Working on Python code:
# API auto-reloads via uvicorn --reload
# Edit code, see changes immediately
Working on Go code:
# Rebuild just the SSE service
docker compose up -d --build sse
Working on Drupal:
# Changes to PHP files are immediate (volume mounted)
# For module changes, clear cache
docker compose exec drupal drush cr
Running Python tests:
docker compose exec api uv run pytest
Each language has its own tooling. No weird cross-language build scripts.
Production Deployment
In production, each service deploys independently:
- Go SSE — Single binary, ~15MB container, scales horizontally
- Python API/Worker — Separate deployments, shared image, different commands
- Drupal — Standard PHP deployment, could be on separate infrastructure entirely
The monorepo is a development convenience. Production sees independent services that happen to share a git history.
CI/CD: In CI, each service can be built and tested independently (Go tests, Python tests, Drupal tests) before building production images. The monorepo ensures they share a single source of truth for configuration, while path-based triggers can skip unchanged services.
What I'd Do Differently
More shared configuration: Environment variables are duplicated across services. A shared config layer (Consul, AWS Parameter Store) would reduce duplication.
Unified logging format: Each language logs differently. A structured logging standard (JSON with consistent fields) would make aggregation easier.
Health check consistency: Go uses /health, Python uses /health, Drupal uses... nothing by default. Standardize this from day one.
Lessons Learned
1. Polyglot is fine if services are truly isolated
The Go service doesn't import Python. Python doesn't call PHP. They communicate through Redis and HTTP. This makes the polyglot aspect invisible day-to-day.
The mistake isn't using multiple languages; it's letting them share code or databases directly instead of talking over well-defined APIs and queues.
2. One Dockerfile per deployable unit
Don't try to build Go and Python in the same Dockerfile. Each language has its own build tooling and runtime. Keep them separate.
3. Docker Compose is the integration layer
Locally, docker-compose.yml is the single source of truth for how services connect. It documents the architecture better than any diagram.
4. Monorepo works for small teams
The ability to make cross-cutting changes in one PR outweighs the downsides. Split into multiple repos when team size demands it, not before.
Wrapping Up
A polyglot project doesn't have to be complicated. Keep each language ecosystem self-contained, communicate only through network protocols, and use Docker Compose to tie it all together. The result is a codebase where Go developers can work on Go, Python developers on Python, and PHP developers on PHP—without stepping on each other.
This structure works for any multi-language project: microservices, legacy integration, or just picking the best tool for each job.
You can see this architecture in action on the live demo—Go, Python, and PHP working together seamlessly.
Final post in the Meeting Intelligence Platform series.
What I designed and built for this project:
- Project structure for Go + Python + Drupal in one repository
- Docker and Docker Compose setup for local dev and production images
- Service boundaries and communication via HTTP and Redis
- Deployment strategy for independent service scaling
Building a multi-language system? I can help with:
- Project structure for polyglot codebases
- Docker and Docker Compose orchestration
- CI/CD pipelines for multi-language builds
- Service integration patterns
[Get in touch on Upwork] | [Get in Touch Directly →] to discuss your architecture.