Web Application Stack Example¶
This example demonstrates how to orchestrate a complete web application stack using Go Overlay. The stack includes Redis for caching, a database migration service, and a web server with proper dependency management.
Overview¶
In this example, we'll set up a typical web application stack with the following services:
- Redis: In-memory cache and session store
- Database Migration: Runs database schema migrations before the application starts
- Web Server: The main application server that depends on Redis and completed migrations
This configuration demonstrates:
- Service dependencies with
depends_on - Startup delays with
wait_after - One-time initialization tasks
- Critical service monitoring with
required
Architecture¶
graph TD
A[Redis] --> C[Web Server]
B[DB Migration] --> C
A -.wait 2s.-> C
B -.wait 1s.-> C The web server depends on both Redis and the database migration service. Redis has a 2-second wait period to ensure it's fully ready, while the migration service has a 1-second wait period.
Complete Configuration¶
Here's the complete services.toml file for this stack:
# Global timeout configurations
[timeouts]
post_script_timeout = 30
service_shutdown_timeout = 10
global_shutdown_timeout = 30
dependency_wait_timeout = 60
# Redis Cache Service
[[services]]
name = "redis"
command = "/usr/bin/redis-server"
args = ["--bind", "0.0.0.0", "--port", "6379", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
enabled = true
required = true
# Database Migration Service (runs once and exits)
[[services]]
name = "db-migration"
command = "/app/migrate"
args = ["-path", "/app/migrations", "-database", "postgres://user:pass@db:5432/myapp?sslmode=disable", "up"]
enabled = true
required = false
depends_on = []
# Web Application Server
[[services]]
name = "web-server"
command = "/app/server"
args = ["--port", "8080", "--redis", "localhost:6379"]
enabled = true
required = true
depends_on = ["redis", "db-migration"]
wait_after = { redis = 2, db-migration = 1 }
user = "appuser"
pre_script = """
#!/bin/bash
echo "Starting web server..."
echo "Checking Redis connection..."
redis-cli -h localhost -p 6379 ping || exit 1
"""
pos_script = """
#!/bin/bash
echo "Web server stopped gracefully"
# Cleanup tasks here
"""
Service Breakdown¶
1. Redis Service¶
Role: Provides in-memory caching and session storage for the web application.
Configuration Parameters:
| Parameter | Value | Description |
|---|---|---|
name | redis | Service identifier |
command | /usr/bin/redis-server | Redis server binary |
args | Various | Bind to all interfaces, set memory limits |
enabled | true | Service starts automatically |
required | true | System shuts down if Redis fails |
Why it's required: Redis is critical for application performance and session management. If it fails, the application cannot function properly.
Arguments Explained:
--bind 0.0.0.0: Listen on all network interfaces--port 6379: Standard Redis port--maxmemory 256mb: Limit memory usage to 256MB--maxmemory-policy allkeys-lru: Evict least recently used keys when memory limit is reached
2. Database Migration Service¶
Role: Executes database schema migrations before the web server starts. This is a one-time task that runs and exits.
Configuration Parameters:
| Parameter | Value | Description |
|---|---|---|
name | db-migration | Service identifier |
command | /app/migrate | Migration tool binary |
args | Migration parameters | Path to migrations and database URL |
enabled | true | Runs on startup |
required | false | System continues if migrations fail (use with caution) |
depends_on | [] | No dependencies |
Why it's not required: Depending on your use case, you might want the system to continue even if migrations fail (e.g., if migrations are already applied). However, in production, you might want to set this to true.
Arguments Explained:
-path /app/migrations: Directory containing migration files-database postgres://...: PostgreSQL connection stringup: Apply all pending migrations
Migration Best Practices
- Always test migrations in a staging environment first
- Consider making this service
required = truein production - Use migration tools that support rollback (like golang-migrate, Flyway, or Liquibase)
- Keep migrations idempotent when possible
3. Web Server Service¶
Role: The main application server that handles HTTP requests, uses Redis for caching, and requires completed database migrations.
Configuration Parameters:
| Parameter | Value | Description |
|---|---|---|
name | web-server | Service identifier |
command | /app/server | Application binary |
args | Server parameters | Port and Redis connection |
enabled | true | Starts automatically |
required | true | Critical service |
depends_on | ["redis", "db-migration"] | Waits for dependencies |
wait_after | { redis = 2, db-migration = 1 } | Additional wait times |
user | appuser | Runs as non-root user |
pre_script | Health checks | Validates Redis before starting |
pos_script | Cleanup | Runs after service stops |
Why it's required: This is the main application service. If it fails, the entire system should shut down.
Dependency Chain:
- Redis starts first (no dependencies)
- DB Migration starts (no dependencies, can run in parallel with Redis)
- After both complete, Go Overlay waits:
- 2 seconds after Redis is running
- 1 second after DB Migration completes
- Web Server starts
Pre-script Explained:
The pre-script validates that Redis is accessible before starting the web server:
#!/bin/bash
echo "Starting web server..."
echo "Checking Redis connection..."
redis-cli -h localhost -p 6379 ping || exit 1
If Redis doesn't respond to PING, the script exits with code 1, preventing the web server from starting.
Post-script Explained:
The post-script runs after the web server stops, allowing for cleanup tasks:
You could add tasks like:
- Flushing remaining cache entries
- Closing database connections
- Sending shutdown notifications
Startup Sequence¶
Here's what happens when you start this stack:
- T+0s: Go Overlay starts
- T+0s: Redis and DB Migration start in parallel (no dependencies)
- T+2s: Redis is running and healthy
- T+3s: DB Migration completes
- T+4s: Wait period for Redis (2s) completes
- T+4s: Wait period for DB Migration (1s) completes
- T+4s: Pre-script runs and validates Redis connection
- T+4s: Web Server starts
Running the Stack¶
Using Go Overlay Directly¶
# Start in daemon mode
go-overlay daemon --config /services.toml
# Check status
go-overlay status
# View logs
journalctl -u go-overlay -f
Expected Output¶
[INFO] Starting service: redis
[INFO] Starting service: db-migration
[INFO] Service redis is running (PID: 1234)
[INFO] Service db-migration completed successfully
[INFO] Waiting 2s after redis startup
[INFO] Waiting 1s after db-migration completion
[INFO] Running pre-script for web-server
[INFO] Starting service: web-server
[INFO] Service web-server is running (PID: 1235)
[INFO] All services started successfully
Customization Options¶
Adding More Services¶
You can extend this stack with additional services:
# Background Worker
[[services]]
name = "worker"
command = "/app/worker"
args = ["--queue", "default", "--redis", "localhost:6379"]
enabled = true
required = false
depends_on = ["redis", "web-server"]
wait_after = { redis = 2 }
user = "appuser"
# Nginx Reverse Proxy
[[services]]
name = "nginx"
command = "/usr/sbin/nginx"
args = ["-g", "daemon off;"]
enabled = true
required = true
depends_on = ["web-server"]
wait_after = { web-server = 3 }
Adjusting Wait Times¶
Modify wait_after values based on your services' startup times:
# For slower services
wait_after = { redis = 5, db-migration = 2 }
# For faster services
wait_after = { redis = 1, db-migration = 0 }
Environment-Specific Configuration¶
Use environment variables in your commands:
[[services]]
name = "web-server"
command = "/app/server"
args = [
"--port", "${PORT:-8080}",
"--redis", "${REDIS_URL:-localhost:6379}",
"--env", "${APP_ENV:-production}"
]
Troubleshooting¶
Web Server Won't Start¶
Symptom: Web server fails to start or crashes immediately.
Possible Causes:
- Redis is not accessible
- Check:
redis-cli -h localhost -p 6379 ping -
Solution: Increase
wait_aftertime for Redis -
Database migrations failed
- Check migration logs
-
Solution: Fix migration errors or set
required = falsetemporarily -
Pre-script validation fails
- Check pre-script output in logs
- Solution: Adjust validation logic or increase wait times
Services Start in Wrong Order¶
Symptom: Web server starts before dependencies are ready.
Solution: Verify depends_on and wait_after configuration:
depends_on = ["redis", "db-migration"] # Must list all dependencies
wait_after = { redis = 2, db-migration = 1 } # Adjust timing
Redis Connection Refused¶
Symptom: Pre-script fails with "Connection refused".
Possible Causes:
- Redis hasn't finished initializing
-
Solution: Increase
wait_after.redisvalue -
Redis is binding to wrong interface
-
Solution: Check Redis
--bindargument -
Port conflict
- Solution: Change Redis port or check for conflicts
Best Practices¶
-
Always use
required = truefor critical services: If your application can't function without a service, mark it as required. -
Set appropriate wait times: Don't use arbitrary values. Test your services to determine actual startup times.
-
Use pre-scripts for validation: Validate dependencies before starting services to fail fast.
-
Run services as non-root users: Use the
userparameter for security. -
Keep migrations idempotent: Ensure migrations can be run multiple times safely.
-
Monitor service health: Use the
go-overlay statuscommand to check service states. -
Test dependency chains: Verify that services start in the correct order during development.
-
Use meaningful service names: Choose names that clearly indicate the service's purpose.
Next Steps¶
- Learn about Docker Integration to containerize this stack
- Explore Common Patterns for more configuration examples
- Read about Dependency Management for advanced scenarios