Skip to content

Advanced Configuration

This guide covers advanced configuration topics including dependency management, service ordering, and complex multi-service scenarios.

Dependency Management

Go Overlay supports explicit service dependencies using the depends_on field. This ensures services start in the correct order and that dependent services wait for their dependencies to be ready.

Basic Dependencies

Use depends_on to specify which services must be running before a service starts:

[[services]]
name = "database"
command = "/usr/bin/postgres"

[[services]]
name = "web-app"
command = "/app/server"
depends_on = ["database"]  # Wait for database to be RUNNING

Behavior: - web-app will not start until database reaches RUNNING state - If database fails to start, web-app will not start - Subject to dependency_wait_timeout configuration

Multiple Dependencies

A service can depend on multiple other services:

[[services]]
name = "database"
command = "/usr/bin/postgres"

[[services]]
name = "cache"
command = "/usr/bin/redis-server"

[[services]]
name = "web-app"
command = "/app/server"
depends_on = ["database", "cache"]  # Wait for both

Behavior: - web-app waits for both database and cache to be RUNNING - Dependencies can start in parallel - web-app starts only after all dependencies are ready

Dependency Chains

Dependencies can form chains where services depend on other dependent services:

[[services]]
name = "database"
command = "/usr/bin/postgres"

[[services]]
name = "migration"
command = "/app/migrate"
depends_on = ["database"]

[[services]]
name = "web-app"
command = "/app/server"
depends_on = ["migration"]  # Indirectly depends on database

Startup order: 1. database starts first 2. migration starts after database is RUNNING 3. web-app starts after migration is RUNNING

Wait After Delays

The wait_after field adds a delay after a service starts before dependent services begin starting. This is useful for services that need initialization time after the process starts.

Basic Wait After

[[services]]
name = "database"
command = "/usr/bin/postgres"
wait_after = 10  # Wait 10 seconds after starting

[[services]]
name = "web-app"
command = "/app/server"
depends_on = ["database"]

Behavior: 1. database process starts 2. database reaches RUNNING state 3. Wait 10 seconds 4. web-app can now start

When to Use wait_after

Use wait_after when: - Service needs time to initialize after process starts - Service listens on a port but isn't ready immediately - Service needs to warm up caches or load data - Service performs internal health checks

Examples:

Database initialization:

[[services]]
name = "postgres"
command = "/usr/bin/postgres"
wait_after = 15  # Time to initialize database, load extensions

Application warm-up:

[[services]]
name = "java-app"
command = "/usr/bin/java"
args = ["-jar", "app.jar"]
wait_after = 30  # JVM startup and application initialization

Cache population:

[[services]]
name = "redis"
command = "/usr/bin/redis-server"
wait_after = 5  # Time to load RDB file from disk

Circular Dependency Detection

Go Overlay automatically detects circular dependencies and prevents system startup if found.

What is a Circular Dependency?

A circular dependency occurs when services depend on each other in a cycle:

# ❌ This configuration will fail
[[services]]
name = "service-a"
depends_on = ["service-b"]

[[services]]
name = "service-b"
depends_on = ["service-a"]

Error: Go Overlay will detect this cycle and exit with an error during startup.

Complex Circular Dependencies

Cycles can involve multiple services:

# ❌ This configuration will fail
[[services]]
name = "service-a"
depends_on = ["service-c"]

[[services]]
name = "service-b"
depends_on = ["service-a"]

[[services]]
name = "service-c"
depends_on = ["service-b"]

Cycle: A → C → B → A

Avoiding Circular Dependencies

Problem: Two services that need each other

Solution 1: Remove the dependency

# If services can start independently, remove depends_on
[[services]]
name = "service-a"
command = "/app/service-a"

[[services]]
name = "service-b"
command = "/app/service-b"

Solution 2: Introduce an intermediary

# Use a shared dependency instead
[[services]]
name = "shared-resource"
command = "/app/resource"

[[services]]
name = "service-a"
depends_on = ["shared-resource"]

[[services]]
name = "service-b"
depends_on = ["shared-resource"]

Solution 3: Use wait_after without depends_on

# Start both, but delay one
[[services]]
name = "service-a"
command = "/app/service-a"

[[services]]
name = "service-b"
command = "/app/service-b"
wait_after = 10  # Give service-a time to start

Complex Multi-Service Examples

Example 1: Web Application Stack

Complete stack with database, cache, worker, and web server:

[timeouts]
post_script_timeout = 30
service_shutdown_timeout = 60
global_shutdown_timeout = 180
dependency_wait_timeout = 300

# Database - Foundation service
[[services]]
name = "postgres"
command = "/usr/lib/postgresql/15/bin/postgres"
args = ["-D", "/var/lib/postgresql/data"]
pre_script = "/scripts/init-postgres.sh"
user = "postgres"
enabled = true
required = true
wait_after = 10  # Database needs time to initialize

# Cache - Independent of database
[[services]]
name = "redis"
command = "/usr/bin/redis-server"
args = ["/etc/redis/redis.conf"]
user = "redis"
enabled = true
required = false
wait_after = 3

# Database migrations - Depends on database
[[services]]
name = "db-migration"
command = "/app/migrate"
args = ["up"]
depends_on = ["postgres"]
user = "app"
enabled = true
required = true

# Background worker - Depends on database and cache
[[services]]
name = "celery-worker"
command = "/app/venv/bin/celery"
args = ["-A", "tasks", "worker", "--loglevel=info"]
depends_on = ["postgres", "redis", "db-migration"]
user = "app"
enabled = true
required = false

# Web application - Depends on migrations
[[services]]
name = "web-app"
command = "/app/venv/bin/gunicorn"
args = ["app:app", "--bind", "0.0.0.0:8000", "--workers", "4"]
depends_on = ["db-migration", "redis"]
pos_script = "/app/healthcheck.sh"
user = "app"
enabled = true
required = true
wait_after = 5

# Reverse proxy - Depends on web app
[[services]]
name = "nginx"
command = "/usr/sbin/nginx"
args = ["-g", "daemon off;"]
depends_on = ["web-app"]
user = "www-data"
enabled = true
required = true

Startup sequence: 1. postgres and redis start in parallel (no dependencies) 2. Wait 10 seconds for postgres, 3 seconds for redis 3. db-migration starts (depends on postgres) 4. celery-worker and web-app start after migration completes 5. Wait 5 seconds for web-app 6. nginx starts last (depends on web-app)

Example 2: Microservices with Service Mesh

Multiple microservices with a service mesh proxy:

# Service mesh control plane
[[services]]
name = "consul"
command = "/usr/bin/consul"
args = ["agent", "-dev"]
enabled = true
required = true
wait_after = 5

# Service mesh proxy
[[services]]
name = "envoy"
command = "/usr/bin/envoy"
args = ["-c", "/etc/envoy/envoy.yaml"]
depends_on = ["consul"]
enabled = true
required = true
wait_after = 3

# Microservice 1
[[services]]
name = "user-service"
command = "/app/user-service"
depends_on = ["envoy"]
user = "app"
enabled = true
required = true

# Microservice 2
[[services]]
name = "order-service"
command = "/app/order-service"
depends_on = ["envoy"]
user = "app"
enabled = true
required = true

# API Gateway
[[services]]
name = "api-gateway"
command = "/app/gateway"
depends_on = ["user-service", "order-service"]
user = "app"
enabled = true
required = true

Example 3: Data Pipeline

ETL pipeline with staged processing:

# Data source
[[services]]
name = "kafka"
command = "/opt/kafka/bin/kafka-server-start.sh"
args = ["/opt/kafka/config/server.properties"]
user = "kafka"
enabled = true
required = true
wait_after = 15

# Stream processor
[[services]]
name = "flink"
command = "/opt/flink/bin/taskmanager.sh"
args = ["start-foreground"]
depends_on = ["kafka"]
user = "flink"
enabled = true
required = true
wait_after = 10

# Data warehouse
[[services]]
name = "clickhouse"
command = "/usr/bin/clickhouse-server"
args = ["--config-file=/etc/clickhouse-server/config.xml"]
user = "clickhouse"
enabled = true
required = true
wait_after = 10

# ETL job
[[services]]
name = "etl-job"
command = "/app/etl"
depends_on = ["flink", "clickhouse"]
user = "app"
enabled = true
required = false

Example 4: Development Environment

Local development stack with hot-reload:

# Database
[[services]]
name = "postgres-dev"
command = "/usr/bin/postgres"
args = ["-D", "/tmp/pgdata"]
enabled = true
required = false
wait_after = 5

# Frontend dev server
[[services]]
name = "vite"
command = "/usr/bin/npm"
args = ["run", "dev"]
enabled = true
required = false

# Backend dev server
[[services]]
name = "backend-dev"
command = "/app/venv/bin/python"
args = ["manage.py", "runserver"]
depends_on = ["postgres-dev"]
enabled = true
required = false

Dependency Best Practices

1. Minimize Dependencies

Only use depends_on when truly necessary:

# ✅ Good: Only essential dependencies
[[services]]
name = "web-app"
depends_on = ["database"]

# ❌ Avoid: Unnecessary dependencies
[[services]]
name = "web-app"
depends_on = ["database", "cache", "metrics", "logging"]

2. Use wait_after Appropriately

Add delays only when needed:

# ✅ Good: Database needs initialization time
[[services]]
name = "postgres"
wait_after = 10

# ❌ Avoid: Unnecessary delay
[[services]]
name = "nginx"
wait_after = 30  # Nginx starts instantly

3. Keep Dependency Chains Short

Avoid deep dependency chains:

# ✅ Good: Flat structure
[[services]]
name = "app"
depends_on = ["database", "cache"]

# ❌ Avoid: Deep chain
# A → B → C → D → E

4. Mark Critical Services as Required

Use required = true for services that must run:

[[services]]
name = "database"
required = true  # System can't function without it

[[services]]
name = "metrics"
required = false  # Nice to have, but not critical

5. Test Dependency Ordering

Verify startup order works correctly:

# Start system and watch logs
go-overlay | grep "Starting service"

# Should see services start in correct order

Troubleshooting Dependencies

Service Won't Start - Waiting for Dependencies

Symptom: Service stuck in PENDING state

Check: 1. Are dependencies running? go-overlay list 2. Is dependency_wait_timeout sufficient? 3. Are dependency names spelled correctly?

Slow Startup

Symptom: System takes long time to start

Solutions: 1. Reduce unnecessary wait_after delays 2. Remove unnecessary dependencies 3. Start independent services in parallel

Circular Dependency Error

Symptom: System won't start, circular dependency detected

Solution: 1. Review dependency graph 2. Remove or restructure dependencies 3. Use wait_after instead of depends_on if appropriate

Next Steps