Files
BarangaySystem/ai-docs/dokploy-deployment-guide.md
2026-06-06 18:43:00 +08:00

518 lines
14 KiB
Markdown

# 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
```