# 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= DB_DATABASE=bukid DB_PORT=3306 DB_USERNAME=mysql DB_PASSWORD= # === Cache/Redis (DragonflyDB) === CACHE_DRIVER=redis REDIS_HOST= REDIS_AUTH= 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 > ``` --- ## 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.-web.rule=Host(`bukid.hesed.sbs`) - traefik.http.routers.-web.entrypoints=web - traefik.http.services.-web.loadbalancer.server.port=9501 - traefik.http.routers.-web.middlewares=redirect-to-https@file - traefik.http.routers.-websecure.rule=Host(`bukid.hesed.sbs`) - traefik.http.routers.-websecure.entrypoints=websecure - traefik.http.services.-websecure.loadbalancer.server.port=9501 - traefik.http.routers.-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 php artisan migrate docker exec 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 --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 --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= DB_DATABASE=bukid DB_PORT=3306 DB_USERNAME=mysql DB_PASSWORD= # Redis / DragonflyDB CACHE_DRIVER=redis REDIS_HOST= REDIS_AUTH= 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 # List Swarm services (for Dokploy Databases) docker service ls # Check container health docker ps | grep # View container logs docker logs --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 # 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 ```