initial: bootstrap from BukidBountyApp base
This commit is contained in:
517
ai-docs/dokploy-deployment-guide.md
Normal file
517
ai-docs/dokploy-deployment-guide.md
Normal file
@@ -0,0 +1,517 @@
|
||||
# Dokploy Deployment Guide — BukidBountyApp (Hypervel/Swoole)
|
||||
|
||||
> This guide documents the exact process for deploying BukidBountyApp (and derivative projects) on a Dokploy-managed server. It is based on real troubleshooting and verified configuration.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Architecture Overview](#architecture-overview)
|
||||
2. [Prerequisites on Dokploy Server](#prerequisites-on-dokploy-server)
|
||||
3. [Step 1: Create Infrastructure Services](#step-1-create-infrastructure-services)
|
||||
4. [Step 2: Deploy the Application](#step-2-deploy-the-application)
|
||||
5. [Step 3: Configure Environment Variables](#step-3-configure-environment-variables)
|
||||
6. [Step 4: Configure Domain & HTTPS](#step-4-configure-domain--https)
|
||||
7. [Step 5: Create the Database](#step-5-create-the-database)
|
||||
8. [Step 6: Run Migrations & Seed](#step-6-run-migrations--seed)
|
||||
9. [Deploying Test/Beta Variants](#deploying-testbeta-variants)
|
||||
10. [Troubleshooting](#troubleshooting)
|
||||
11. [Reference: Working Configuration Files](#reference-working-configuration-files)
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
Internet → Traefik (ports 80/443) → dokploy-network → bukidapp container (port 9501)
|
||||
→ DragonflyDB (port 6379)
|
||||
→ MySQL (port 3306)
|
||||
```
|
||||
|
||||
| Component | Type | Network |
|
||||
|-------------------|--------------------------|------------------|
|
||||
| BukidBountyApp | Docker Compose (Dokploy) | `dokploy-network`|
|
||||
| MySQL 8 | Dokploy Database (Swarm) | `dokploy-network`|
|
||||
| DragonflyDB | Docker Compose (Dokploy) | `dokploy-network`|
|
||||
| Traefik | Managed by Dokploy | `dokploy-network`|
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites on Dokploy Server
|
||||
|
||||
- Dokploy installed and running
|
||||
- Traefik configured with Let's Encrypt cert resolver
|
||||
- DNS pointing your domain to the server IP
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Infrastructure Services
|
||||
|
||||
### 1a. Create MySQL Database
|
||||
|
||||
1. In Dokploy UI → **Create** → **Database** → **MySQL**
|
||||
2. Set root password, default user, and password
|
||||
3. Dokploy will create a Swarm service (e.g., `prodservers-prodsqlmain-kv3yph`)
|
||||
|
||||
> **⚠️ IMPORTANT:**
|
||||
> The `DB_HOST` for your app is the **Swarm service name**, not the container name.
|
||||
> For Swarm services, use the service name directly (e.g., `prodservers-prodsqlmain-kv3yph`).
|
||||
|
||||
**Find the MySQL service name (run on Hostinger server terminal):**
|
||||
```bash
|
||||
docker service ls | grep -i sql
|
||||
```
|
||||
|
||||
### 1b. Create DragonflyDB (Redis Alternative)
|
||||
|
||||
1. In Dokploy UI → **Create** → **Compose** → new project for DragonflyDB
|
||||
2. Use this `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
dragonflydb:
|
||||
image: 'docker.dragonflydb.io/dragonflydb/dragonfly'
|
||||
ulimits:
|
||||
memlock: -1
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- dragonflydata:/data
|
||||
environment:
|
||||
- DFLY_requirepass
|
||||
networks:
|
||||
- dokploy-network
|
||||
|
||||
volumes:
|
||||
dragonflydata:
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
external: true
|
||||
```
|
||||
|
||||
3. Deploy the service
|
||||
|
||||
> **⚠️ IMPORTANT:**
|
||||
> The `REDIS_HOST` for your app is the **full container name**, not the Dokploy project name.
|
||||
> For Docker Compose services, the container name follows the pattern: `{project-name}-{service-name}-{replica}`.
|
||||
|
||||
**Find the DragonflyDB container name (run on Hostinger server terminal):**
|
||||
```bash
|
||||
docker ps --format '{{.Names}}' | grep dragonfly
|
||||
# Example output: prodservers-dragonflydb-rnfaje-dragonflydb-1
|
||||
```
|
||||
|
||||
> **🔴 CRITICAL DISTINCTION:**
|
||||
> - **Swarm services** (Dokploy Databases) → use the **service name** as hostname
|
||||
> - **Compose services** (like DragonflyDB) → use the **container name** as hostname
|
||||
>
|
||||
> These are different! Always verify with `docker service ls` or `docker ps --format '{{.Names}}'`.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Deploy the Application
|
||||
|
||||
1. In Dokploy UI → **Create** → **Compose**
|
||||
2. Connect to your Git repository (e.g., `git@git.cr8.space:josh/BukidBountyApp.git`)
|
||||
3. Set branch to `main`
|
||||
4. The `docker-compose.yml` in the repo will be used automatically
|
||||
|
||||
### Working `docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
bukidapp:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.php
|
||||
ports:
|
||||
- 9501
|
||||
networks:
|
||||
- dokploy-network
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "curl -f http://localhost:9501/health || exit 1" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
external: true
|
||||
```
|
||||
|
||||
> **🔴 CAUTION:**
|
||||
> Do **NOT** use host port mapping like `9522:9501`. Just expose the internal port `9501`.
|
||||
> Traefik handles external routing via labels that Dokploy injects automatically.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Configure Environment Variables
|
||||
|
||||
In Dokploy UI → your service → **Environment** tab, set:
|
||||
|
||||
```env
|
||||
# === Database (MySQL) ===
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=<mysql-swarm-service-name>
|
||||
DB_DATABASE=bukid
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=mysql
|
||||
DB_PASSWORD=<your-mysql-password>
|
||||
|
||||
# === Cache/Redis (DragonflyDB) ===
|
||||
CACHE_DRIVER=redis
|
||||
REDIS_HOST=<dragonflydb-container-name>
|
||||
REDIS_AUTH=<your-dragonfly-password>
|
||||
REDIS_PORT=6379
|
||||
REDIS_DB=0
|
||||
```
|
||||
|
||||
### How to Find the Correct Hostnames
|
||||
|
||||
| Service | Type | How to Find Hostname | Example |
|
||||
|-------------|--------|------------------------------------------------------------|-------------------------------------------------------|
|
||||
| MySQL | Swarm | `docker service ls \| grep sql` | `prodservers-prodsqlmain-kv3yph` |
|
||||
| DragonflyDB | Compose| `docker ps --format '{{.Names}}' \| grep dragonfly` | `prodservers-dragonflydb-rnfaje-dragonflydb-1` |
|
||||
| PostgreSQL | Swarm | `docker service ls \| grep pgsql` | `prodservers-prodpgsqlmain-x8frsw` |
|
||||
|
||||
> **💡 TIP:**
|
||||
> You can verify connectivity from the app container using Dokploy's container terminal (UI):
|
||||
> ```bash
|
||||
> ping -c 2 <hostname>
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Configure Domain & HTTPS
|
||||
|
||||
1. In Dokploy UI → your service → **Domains** tab
|
||||
2. Add domain:
|
||||
- **Host**: `bukid.hesed.sbs` (or your domain)
|
||||
- **Container Port**: `9501`
|
||||
- **HTTPS**: Enabled
|
||||
- **Certificate**: Let's Encrypt
|
||||
3. **Deploy/Redeploy** the service
|
||||
|
||||
### What Dokploy Adds Automatically
|
||||
|
||||
Dokploy injects these Traefik labels into your compose at deploy time:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- traefik.docker.network=dokploy-network
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.<id>-web.rule=Host(`bukid.hesed.sbs`)
|
||||
- traefik.http.routers.<id>-web.entrypoints=web
|
||||
- traefik.http.services.<id>-web.loadbalancer.server.port=9501
|
||||
- traefik.http.routers.<id>-web.middlewares=redirect-to-https@file
|
||||
- traefik.http.routers.<id>-websecure.rule=Host(`bukid.hesed.sbs`)
|
||||
- traefik.http.routers.<id>-websecure.entrypoints=websecure
|
||||
- traefik.http.services.<id>-websecure.loadbalancer.server.port=9501
|
||||
- traefik.http.routers.<id>-websecure.tls.certresolver=letsencrypt
|
||||
```
|
||||
|
||||
> **⚠️ IMPORTANT:**
|
||||
> You do **NOT** need to add these labels manually in your `docker-compose.yml`.
|
||||
> Dokploy handles them when you configure the domain in the UI.
|
||||
> Adding them manually can cause **label conflicts**.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Create the Database
|
||||
|
||||
The MySQL database must be created manually after the MySQL service is running.
|
||||
|
||||
**Via Hostinger server terminal:**
|
||||
|
||||
```bash
|
||||
# Connect to MySQL container
|
||||
docker exec -it $(docker ps --format '{{.Names}}' | grep prodsqlmain) mysql -u root -p
|
||||
```
|
||||
|
||||
**Inside MySQL shell:**
|
||||
|
||||
```sql
|
||||
CREATE DATABASE bukid;
|
||||
GRANT ALL PRIVILEGES ON bukid.* TO 'mysql'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
EXIT;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Run Migrations & Seed
|
||||
|
||||
**Via Dokploy UI → your service → Terminal:**
|
||||
|
||||
```bash
|
||||
php artisan migrate
|
||||
php artisan db:seed
|
||||
```
|
||||
|
||||
**Or from the Hostinger server terminal:**
|
||||
|
||||
```bash
|
||||
docker exec <bukidapp-container-name> php artisan migrate
|
||||
docker exec <bukidapp-container-name> php artisan db:seed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deploying Test/Beta Variants
|
||||
|
||||
To deploy a test or beta version of the same app with separate databases:
|
||||
|
||||
### 1. Plan Variant Resources
|
||||
|
||||
| Variant | MySQL DB Name | Redis DB | Domain |
|
||||
|---------|---------------|----------|---------------------------|
|
||||
| Prod | `bukid` | `0` | `bukid.hesed.sbs` |
|
||||
| Beta | `bukid_beta` | `1` | `beta.bukid.hesed.sbs` |
|
||||
| Test | `bukid_test` | `2` | `test.bukid.hesed.sbs` |
|
||||
|
||||
> **💡 TIP:**
|
||||
> You can reuse the **same MySQL server** and **same DragonflyDB instance** for all variants.
|
||||
> Just create different databases in MySQL and use different `REDIS_DB` numbers (0-15).
|
||||
|
||||
### 2. Create Additional MySQL Databases
|
||||
|
||||
**Via Hostinger server terminal:**
|
||||
|
||||
```bash
|
||||
docker exec -it $(docker ps --format '{{.Names}}' | grep prodsqlmain) mysql -u root -p
|
||||
```
|
||||
|
||||
```sql
|
||||
CREATE DATABASE bukid_beta;
|
||||
CREATE DATABASE bukid_test;
|
||||
GRANT ALL PRIVILEGES ON bukid_beta.* TO 'mysql'@'%';
|
||||
GRANT ALL PRIVILEGES ON bukid_test.* TO 'mysql'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
EXIT;
|
||||
```
|
||||
|
||||
### 3. Deploy New Compose Project in Dokploy
|
||||
|
||||
1. In Dokploy UI → **Create** → **Compose**
|
||||
2. Connect to the same Git repo, optionally use a different branch (e.g., `beta`, `develop`)
|
||||
3. Set environment variables with variant-specific values:
|
||||
|
||||
```env
|
||||
DB_DATABASE=bukid_beta
|
||||
REDIS_DB=1
|
||||
# DB_HOST and REDIS_HOST remain the same (same infrastructure)
|
||||
```
|
||||
|
||||
4. Configure domain in Domains tab: `beta.bukid.hesed.sbs` → port `9501`
|
||||
5. Deploy
|
||||
6. Run migrations via Dokploy container terminal:
|
||||
```bash
|
||||
php artisan migrate
|
||||
php artisan db:seed
|
||||
```
|
||||
|
||||
### 4. Verify Each Variant
|
||||
|
||||
**Via Hostinger server terminal:**
|
||||
|
||||
```bash
|
||||
# Find all bukid containers
|
||||
docker ps --format '{{.Names}}' | grep bukid
|
||||
|
||||
# Check health of each — must show (healthy)
|
||||
docker ps | grep bukid
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 404 Not Found (Domain Access)
|
||||
|
||||
**Check this in order:**
|
||||
|
||||
1. **Container health** — Must show `(healthy)`, NOT `(unhealthy)`
|
||||
```bash
|
||||
docker ps | grep bukidapp
|
||||
```
|
||||
2. **If unhealthy**, check app logs:
|
||||
```bash
|
||||
docker logs <container-name> --tail 100
|
||||
```
|
||||
3. **Most common cause**: Redis or MySQL DNS resolution failure → wrong hostname in env vars
|
||||
4. **Network**: Verify container is on `dokploy-network`:
|
||||
```bash
|
||||
docker network inspect dokploy-network --format '{{range .Containers}}{{.Name}} {{end}}' | tr ' ' '\n' | grep bukid
|
||||
```
|
||||
5. **Redeploy**: Domain/label changes require a full **Redeploy** in Dokploy UI, not just restart
|
||||
|
||||
### Redis DNS Lookup Failed
|
||||
|
||||
```
|
||||
RedisException: DNS Lookup resolve failed
|
||||
```
|
||||
|
||||
- **Cause**: Wrong `REDIS_HOST` value
|
||||
- **Fix**: Use full container name → `docker ps --format '{{.Names}}' | grep dragonfly`
|
||||
|
||||
### MySQL Access Denied
|
||||
|
||||
```
|
||||
SQLSTATE[HY000] [1044] Access denied for user 'mysql'@'%' to database 'bukid'
|
||||
```
|
||||
|
||||
- **Cause**: Database doesn't exist or user lacks permissions
|
||||
- **Fix**: Create the database and grant permissions (see [Step 5](#step-5-create-the-database))
|
||||
|
||||
### Traefik Logs Empty
|
||||
|
||||
```bash
|
||||
docker logs dokploy-traefik --tail 50
|
||||
```
|
||||
|
||||
If empty, Traefik hasn't encountered errors — the issue is likely the container being unhealthy.
|
||||
|
||||
### Container Keeps Restarting
|
||||
|
||||
Check logs for startup errors:
|
||||
```bash
|
||||
docker logs <container-name> --tail 200
|
||||
```
|
||||
|
||||
Common causes:
|
||||
- Missing `.env` variables
|
||||
- Database not reachable
|
||||
- Redis not reachable
|
||||
- PHP extension missing
|
||||
|
||||
---
|
||||
|
||||
## Reference: Working Configuration Files
|
||||
|
||||
### `docker-compose.yml` (committed in repo root)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
bukidapp:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.php
|
||||
ports:
|
||||
- 9501
|
||||
networks:
|
||||
- dokploy-network
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "curl -f http://localhost:9501/health || exit 1" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
external: true
|
||||
```
|
||||
|
||||
### `Dockerfile.php`
|
||||
|
||||
```dockerfile
|
||||
FROM hyperf/hyperf:8.3-alpine-v3.19-swoole-v6
|
||||
|
||||
USER root
|
||||
|
||||
RUN apk add --no-cache \
|
||||
postgresql-dev \
|
||||
php83-pdo_pgsql \
|
||||
php83-pgsql \
|
||||
7zip
|
||||
|
||||
RUN apk add --no-cache curl \
|
||||
&& curl -fsSL https://unofficial-builds.nodejs.org/download/release/v20.19.0/node-v20.19.0-linux-x64-musl.tar.gz \
|
||||
| tar -xz -C /usr/local --strip-components=1 \
|
||||
&& node -v \
|
||||
&& npm -v
|
||||
|
||||
RUN echo "extension=pdo_pgsql.so" > /etc/php83/conf.d/01_pdo_pgsql.ini && \
|
||||
echo "extension=pgsql.so" > /etc/php83/conf.d/00_pgsql.ini
|
||||
|
||||
WORKDIR /var/app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 9501
|
||||
|
||||
COPY composer.json composer.lock ./
|
||||
RUN composer install --no-dev --optimize-autoloader
|
||||
|
||||
CMD ["php", "artisan", "serve"]
|
||||
```
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
```env
|
||||
# Database
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=<mysql-swarm-service-name>
|
||||
DB_DATABASE=bukid
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=mysql
|
||||
DB_PASSWORD=<password>
|
||||
|
||||
# Redis / DragonflyDB
|
||||
CACHE_DRIVER=redis
|
||||
REDIS_HOST=<dragonflydb-container-name>
|
||||
REDIS_AUTH=<password>
|
||||
REDIS_PORT=6379
|
||||
REDIS_DB=0
|
||||
```
|
||||
|
||||
### `/health` Route (in `routes/web.php`)
|
||||
|
||||
```php
|
||||
Route::get('/health', function () {
|
||||
return 'OK';
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: Useful Commands
|
||||
|
||||
All commands are run on the **Hostinger server terminal** unless otherwise noted.
|
||||
|
||||
```bash
|
||||
# List all running containers
|
||||
docker ps
|
||||
|
||||
# Find a specific container name
|
||||
docker ps --format '{{.Names}}' | grep <keyword>
|
||||
|
||||
# List Swarm services (for Dokploy Databases)
|
||||
docker service ls
|
||||
|
||||
# Check container health
|
||||
docker ps | grep <container>
|
||||
|
||||
# View container logs
|
||||
docker logs <container-name> --tail 100
|
||||
|
||||
# Inspect dokploy-network's connected containers
|
||||
docker network inspect dokploy-network --format '{{range .Containers}}{{.Name}} {{end}}'
|
||||
|
||||
# Test connectivity (run in Dokploy container terminal via UI)
|
||||
ping -c 2 <target-hostname>
|
||||
|
||||
# Run artisan commands (via Dokploy container terminal via UI)
|
||||
php artisan migrate
|
||||
php artisan db:seed
|
||||
|
||||
# Connect to MySQL (via Hostinger server terminal)
|
||||
docker exec -it $(docker ps --format '{{.Names}}' | grep prodsqlmain) mysql -u root -p
|
||||
```
|
||||
Reference in New Issue
Block a user