518 lines
14 KiB
Markdown
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
|
|
```
|