If you’ve tried deploying a Laravel application on Dokploy (the self-hosted Heroku alternative built on Docker) and immediately ran into a wall of Database connection refused or SQLSTATE[HY000] [2002] errors, you are not alone.
It works perfectly on your local machine, but the moment it hits the server, it falls apart. Here is a real-world breakdown of the database connectivity issues we experienced, why they happened, and how to fix them.
1. The "Localhost" Trap (The 127.0.0.1 Connection Refused)
The Issue
On your local computer, you probably set your .env to:
DB_HOST=127.0.0.1
DB_PORT=3306
When you deploy this to Dokploy, your Laravel application crashes on start because it cannot connect to the database.
The Reason
Dokploy runs everything in isolated Docker Containers.
Inside a container, 127.0.0.1 or localhost refers to the Laravel container itself, not the host machine or your database container. Since there is no database running inside your Laravel code container, it refuses the connection.
The Solution
You must use Docker's internal networking.
- If your database is created inside Dokploy, Dokploy assigns it a specific network service name (e.g.
dokploy-db-mysqlor the service name you typed in the database creation wizard). - Change your host in the Dokploy Environment Variables UI to match that service name:
DB_HOST=dokploy-db-mysql # Use Dokploy's internal container name
2. The Config Caching Nightmare (Why changing Env Variables in the UI did nothing)
The Issue
You realized the host was wrong, went to the Dokploy dashboard, updated the DB_HOST environment variable to the correct container hostname, saved, and redeployed.
Yet, the app still threw the old connection error looking for 127.0.0.1. You checked the container logs, confirmed the new environment variables were set, but Laravel simply ignored them.
The Reason
This happens if you run configuration caching in your Dockerfile during the build step:
RUN php artisan config:cache
When Laravel builds your container, it runs config:cache and bakes whatever .env values exist at build-time directly into a static cache file (bootstrap/cache/config.php).
At runtime, Laravel completely ignores your Dokploy UI environment variables and loads the baked-in values instead.
The Solution
Never cache your configuration during the Docker build stage if you plan to change environment variables dynamically.
- Remove
RUN php artisan config:cachefrom your build steps. - If you want caching for speed in production, clear it on boot by running
php artisan config:clearorphp artisan optimize:clearinside your container's startup command / entrypoint script.
3. The Race Condition (Migrations crashing the build)
The Issue
You configured your Docker container to automatically run migrations on deployment:
php artisan migrate --force
But your deployment failed during this step because the database connection timed out or was refused, even though all credentials were 100% correct.
The Reason
When Dokploy restarts or provisions your stack, it starts both the Laravel container and the MySQL/PostgreSQL container simultaneously.
Laravel boots up in milliseconds and immediately tries to run migrations. However, MySQL is still executing its slow initial setup scripts, checking schemas, and opening sockets. The database container isn't ready to receive queries yet, so Laravel's migration command crashes, failing the entire Dokploy deployment.
The Solution
You need to make Laravel "wait" for the database to boot. Add a small bash loop to your entrypoint script (entrypoint.sh) that pings the database port before running migrations:
echo "Waiting for MySQL to boot..."
until nc -z -v -w30 $DB_HOST $DB_PORT; do
echo "MySQL is not ready yet. Retrying in 2 seconds..."
sleep 2
done
echo "MySQL is up! Running migrations..."
php artisan migrate --force
4. Port Mapping Confusion (Internal vs. External Ports)
The Issue
Mapping your Dokploy MySQL database port to 3306 (or 3307) externally so you could connect via TablePlus/DBeaver from your laptop, but when configuring your Laravel .env inside Dokploy, you weren't sure which port to use.
The Reason
Docker has two layers of ports:
-
Internal Port: The port the service listens to inside the Docker private network (always
3306for MySQL). -
External/Exposed Port: The port mapped to the public server IP so you can connect from the outside world (e.g., mapping host
3307to container3306).
Inside Dokploy, your Laravel app and your MySQL container share the same private network. Therefore, they communicate internally.
The Solution
- For Laravel (.env on Dokploy): Always use the internal port (
DB_PORT=3306). - For TablePlus/DBeaver (External laptop): Use the exposed host port (
3307) and the public server IP.
Summary Checklist for Dokploy deployments
- Set
DB_HOSTto the internal Docker container/service name, not127.0.0.1. - Set
DB_PORTto the internal port (3306for MySQL), not the exposed host port. - Avoid running
php artisan config:cacheduring Docker build time. - Implement a
wait-for-itcheck in your startup script so migrations don't run before MySQL is fully booted.
Top comments (0)