Docker Deployment Guide
A comprehensive guide to deploying the BowlerKit stack using Docker and Docker Compose for your Expo application.
Docker Deployment Guide
This guide covers the deployment of the BowlerKit ecosystem using Docker. The architecture is designed to be modular, sharing common infrastructure (Database, Redis, Proxy) across all your applications, including your Expo backend and documentation.
🏗️ Architecture Overview
The system is composed of three main layers:
- Shared Proxy: A Caddy-based reverse proxy that handles incoming traffic and SSL.
- Shared Infrastructure: A single MariaDB and Redis instance serving all apps.
- Application Layer: Your Laravel admin-panel, Go api-backend, queue workers, scheduler, and documentation site.
🛠️ Prerequisites
- Docker and Docker Compose installed.
- Git (for cloning and version control).
- Basic knowledge of terminal commands.
🚀 Step-by-Step Setup
1. Initialize Shared Networks
Before starting any services, create the external networks that allow containers to communicate across different Compose projects.
docker network create web
docker network create shared2. Start Shared Infrastructure
Navigate to the infra directory and start the database and cache services.
cd infra
docker compose up -dThis starts:
- MariaDB (Host:
mysql, Port:3306) - Redis (Host:
redis, Port:6379)
3. Setup Reverse Proxy
The proxy handles routing for your domains (e.g., api.yourdomain.com).
cd ../proxy
docker compose up -dFor local development, use the Caddyfile provided which supports *.localhost domains. For production, update the Caddyfile with your real domains and remove auto_https off.
4. Provision Your Application
Run the provisioning script to create a dedicated database and user for your app.
cd ../infra
chmod +x provision-app.sh
./provision-app.sh bowlerplateSave the output! It will provide you with the DB_DATABASE, DB_USERNAME, and DB_PASSWORD for your .env file.
5. Configure the Application
Copy the production environment template and update it with the credentials from the previous step.
cd ../admin-panel
cp .env.example .env.productionEdit .env.production:
DB_HOST=mysqlDB_DATABASE=bowlerplateDB_USERNAME=bowlerplate_userDB_PASSWORD=your_provisioned_passwordREDIS_HOST=redisAPP_URL=https://api.yourdomain.com
Also configure api-backend/.env with the same database credentials and the admin-panel URL.
6. Build and Deploy
Build Documentation Site
The documentation site uses a "pre-built" Docker strategy. You must build it locally first.
cd ../../docs
bun install
bun run buildLaunch the Stack
Now, go back to the admin-panel directory and start the application services documented for this stack.
cd ../admin-panel
docker compose up -d --buildThis compose stack launches:
admin-panel: The Laravel API + admin dashboard running on FrankenPHP (Port 8000).docs: The documentation site running on Bun (Port 3000).queue: The Laravel queue worker.scheduler: The Laravel task scheduler.
If you plan to serve /api/v1/app/* in production, deploy whichever backend receives that prefix: Laravel compatibility mode on admin-panel, or Go performance mode on api-backend. The Caddy route prefixes are in place for both options.
🔧 Useful Commands
Viewing Logs
# All services
docker compose logs -f
# Specific service
docker compose logs -f backendRunning Artisan Commands
docker compose exec admin-panel php artisan migrate
docker compose exec admin-panel php artisan tinkerRestarting Services
docker compose restart admin-panel
docker compose restart api-backend⚠️ Important Notes
- Volumes: Application data and logs are stored in named volumes (e.g.,
app_storage). Do not delete these volumes unless you want to wipe application state. - Permissions: The Dockerfile automatically sets permissions for
storageandbootstrap/cache. If you encounter permission issues, ensure your host directory is writable bywww-data(UID 33). - FrankenPHP: The backend uses FrankenPHP with Octane for high performance. It handles the web server and PHP execution in a single process.
- Expo Connection: In your Expo app's
.env, ensureEXPO_PUBLIC_API_URLpoints to yourAPP_URLconfigured in step 5.
🔀 Caddy Routing for Split Backend
The Caddy configs now implement the canonical split-backend prefixes.
Canonical Prefix-Based Routing
| Prefix | Backend | Port |
|---|---|---|
/api/v1/auth/* | Laravel (admin-panel) | 8000 |
/api/v1/uploads | Laravel (admin-panel) | 8000 |
/api/v1/webhooks/revenuecat | Laravel (admin-panel) | 8000 |
/api/v1/app/* | Laravel compatibility mode or Go performance mode | 8000 or 8080 |
/admin | Laravel (admin-panel) | 8000 |
/ | Landing page | 3000 |
Example Caddy Configuration
# Route the shared app contract (choose Laravel compatibility mode or Go performance mode)
@api_backend path /api/v1/app/* /health
handle @api_backend {
reverse_proxy http://api-backend:8080
}
# Route Laravel auth and Laravel-owned API routes
@laravel_api path /api/v1/auth/* /api/v1/uploads /api/v1/webhooks/revenuecat
handle @laravel_api {
reverse_proxy http://admin-panel:8000
}
# Route admin panel
@admin path /admin /admin/*
handle @admin {
reverse_proxy http://admin-panel:8000
}
# Default: landing page
handle {
reverse_proxy http://landing-page:3000
}Do not add routes outside the shared contract for connected accounts or generic webhooks. Laravel owns /api/v1/auth/connected-accounts*, and the only canonical webhook route here is /api/v1/webhooks/revenuecat.
