How to fix "SQLSTATE[HY000] [2002] Connection refused" Laravel error in GitHub Actions
Last updated on by Ashley Allen
![How to fix "SQLSTATE[HY000] [2002] Connection refused" Laravel error in GitHub Actions image](https://picperf.io/https://laravelnews.s3.amazonaws.com/featured-images/github-actions.png)
GitHub Actions is a popular tool for setting up continuous integration (CI) workflows for your repositories hosted on GitHub. It allows you to define workflows in YAML files that can be used to automate tasks such as running tests, static analysis, and deployments.
A common error you may come across when setting up your GitHub Actions CI workflow for a Laravel application which uses MySQL is "SQLSTATE[HY000] [2002] Connection refused". This error occurs when your application cannot connect to the MySQL database. You'll typically encounter this when running migrations at the start of your tests. When you inspect your workflow's output on GitHub, you might see a message that looks like this:
SQLSTATE[HY000] [2002] Connection refused (Connection: mysql, SQL: select exists (select 1 from information_schema.tables where table_schema = 'testing' and table_name = 'migrations' and table_type in ('BASE TABLE', 'SYSTEM VERSIONED')) as `exists`)at vendor/laravel/framework/src/Illuminate/Database/Connection.php:825 821▕ $this->getName(), $query, $this->prepareBindings($bindings), $e 822▕ ); 823▕ } 824▕➜ 825▕ throw new QueryException( 826▕ $this->getName(), $query, $this->prepareBindings($bindings), $e 827▕ ); 828▕ } 829▕ }
As we can see, the reason for this error is not immediately obvious. We're just told that the connection is being refused.
In this article, we'll take a look at three common reasons you might encounter this error in your GitHub Actions workflow and how to fix them.
Valid GitHub Actions Workflow
Before we look at some of the possible causes of the "SQLSTATE[HY000] [2002] Connection refused" error, let's first look at a basic valid GitHub Actions workflow for a Laravel application that uses MySQL.
We'll keep referring to this example throughout the article to demonstrate the issues and how to fix them.
The following is a GitHub Action workflow that runs tests for a Laravel application using MySQL:
name: Run tests on: [push] jobs: tests: name: Run tests runs-on: ubuntu-latest # Configure the MySQL service services: mysql: image: mysql:8 env: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: testing ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: # Checkout the repository - uses: actions/checkout@v4 with: fetch-depth: 1 # Setup PHP 8.3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 8.3 # Install the Composer dependencies - name: Run composer install run: composer install -n --prefer-dist # Copy the .env.ci file to .env and generate the application key - name: Prepare Laravel Application run: | cp .env.ci .env php artisan key:generate # Run the tests - name: Run tests run: php artisan test
In the workflow above, we're creating a job called tests
that runs on every push to the repository. The job runs on PHP 8.3, MySQL 8, and the latest version of Ubuntu. Before we run the tests, we install the Composer dependencies, copy the .env.ci
file to .env
, generate the application key, and then run the tests.
For this article, we'll assume the .env.ci
file contains the correct environment variables needed to run the workflow on GitHub Actions. The only ones we're interested in are the database connection details that have been set up to connect to the MySQL service:
DB_HOST=127.0.0.1DB_PORT=3306DB_DATABASE=testingDB_USERNAME=rootDB_PASSWORD=password
Now, let's move on to the common causes of the "SQLSTATE[HY000] [2002] Connection refused" error and how to fix them.
Using Incorrect MySQL Port
The most common reason I run into the "SQLSTATE[HY000] [2002] Connection refused" error when connecting to MySQL in GitHub Actions is using an incorrect port number.
Dynamic Port Number
By default, MySQL runs on port 3306
. However, when running it within a service container inside a GitHub Actions workflow, a random port number on the workflow runner will be assigned to the service container's 3306
port. This means you never know which port number to use to connect to MySQL.
For example, let's take this workflow file that instructs GitHub Actions to expose port 3306
on the MySQL service:
name: Run tests on: [push] jobs: tests: name: Run tests runs-on: ubuntu-latest services: mysql: image: mysql:8 env: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: testing ports: - 3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: # ... - name: Run tests run: php artisan test
We're exposing port 3306
on the MySQL service container and allowing GitHub Actions to map it to a random port on the workflow runner. This means we can't rely on port number 3306
to connect to MySQL.
If we were to try running our workflow, we'd encounter the "SQLSTATE[HY000] [2002] Connection refused" error because we're trying to connect to MySQL using port 3306
in our .env.ci
file rather than the port number assigned by GitHub Actions.
However, we can read the port number that GitHub Actions assigns to the 3306
port of the service container using: ${{ job.services.mysql.ports['3306'] }}
. We can use this to override our DB_PORT
environment variable with this value by updating our "Run tests" step like so:
- name: Run tests run: php artisan test env: DB_PORT: ${{ job.services.mysql.ports['3306'] }}
Let's say that the workflow runner was assigned port 1234
for accessing port 3306
of the MySQL container. This would result in the DB_PORT
environment variable being set to 1234
.
Hardcoded Port Number
If you'd prefer to explicitly set the port number that should be used to access the MySQL service, you can do this in the workflow file. This is useful if you want to avoid the random port number assignment by GitHub Actions.
In our valid example, we map port 3306
on the MySQL service to port 3306
on the workflow runner using the syntax:WORKFLOW_RUNNER_PORT:SERVICE_PORT
. In this case, we're using 3306:3306
like so:
services: mysql: image: mysql:8 env: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: testing ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
As a result, we can access the MySQL service using the existing values set in our .env.ci
file since they're set to use port 3306
.
However, there may be times when you'd prefer to use a different port number. You might want to do this if you have clashes with other services running on the same port, such as another MySQL service. In this case, you can change the port number for the MySQL service in the workflow file. For example, let's change the port number to 33306
:
services: mysql: image: mysql:8 env: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: testing ports: - 33306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
Now, to access MySQL, you can either update the .env.ci
file to use the new port number:
DB_HOST=127.0.0.1DB_PORT=33306DB_DATABASE=testingDB_USERNAME=rootDB_PASSWORD=password
Or, you can specify the environment variable in the workflow file, as we showed earlier:
- name: Run tests run: php artisan test env: DB_PORT: ${{ job.services.mysql.ports['3306'] }}
No MySQL Service Defined in the Workflow
Another reason you might encounter the error is if you don't have a MySQL service defined in your workflow. This is an easy mistake to make, especially if you're new to GitHub Actions or you've copied a workflow from somewhere and forgot to include the MySQL service.
Let's take a look at what our workflow would look like without the MySQL service:
name: Run tests on: [push] jobs: tests: name: Run tests runs-on: ubuntu-latest # ⚠️ Missing MySQL service here! ⚠️ steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 8.3 - name: Run composer install run: composer install -n --prefer-dist - name: Prepare Laravel Application run: | cp .env.ci .env php artisan key:generate - name: Run tests run: php artisan test
As we can see, if I hadn't added the warning message in the example above, it might not be immediately obvious that the MySQL service is missing. But if you run this workflow, you'll see the "SQLSTATE[HY000] [2002] Connection refused" error.
To fix this, we can add a MySQL service by defining one like in our valid example:
name: Run tests on: [push] jobs: tests: name: Run tests runs-on: ubuntu-latest # Configure the MySQL service services: mysql: image: mysql:8 env: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: testing ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: # ...
MySQL Is Not Available Yet
Another cause for the "SQLSTATE[HY000] [2002] Connection refused" error is that the mysql
service container has booted, but MySQL itself is not yet available to accept connections and handle queries.
In our valid workflow example, we can see that we have a command defined in the MySQL service options:
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
This command checks whether MySQL is ready to accept connections and prevents the workflow from continuing until it is ready to do so. It runs mysqladmin ping
every 10 seconds with a timeout of five seconds and retries three times. If one of the checks succeeds, MySQL is ready, and the rest of the workflow will continue. If MySQL is not ready after three retries, the workflow will fail.
If we don't have this check in place, once the mysql
service is booted, the workflow will continue to the next step, regardless of whether MySQL is ready to accept connections.
In many cases, MySQL will be ready by the time you run the tests. Other steps tend to be involved before the workflow runner runs the tests, such as setting up PHP, installing Composer dependencies, and preparing the Laravel application. So, this already adds some time before running the tests, and MySQL is typically ready by then. However, this isn't always a guarantee. You might also have other steps in your workflow that need to interact with MySQL early on, which could also cause this issue. So, it's a handy check to have in place.
Without the check in place, your workflow YAML file might look like this:
name: Run tests on: [push] jobs: tests: name: Run tests runs-on: ubuntu-latest services: mysql: image: mysql:8 env: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: testing ports: - 3306:3306 steps: # ...
We can add the check to the bottom of the MySQL service definition:
mysql: image: mysql:8 env: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: testing ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
Summary
In this article, we've looked at three common reasons you might encounter the "SQLSTATE[HY000] [2002] Connection refused" error in your GitHub Actions workflow for a Laravel application that uses MySQL.
As we've seen, the error can occur due to using the wrong port number, not having a MySQL service defined in the workflow, or MySQL not being ready to accept connections.
Hopefully, this article has given you some insight into how to fix this error and ensure your GitHub Actions workflow runs smoothly.
As well as encountering issues with MySQL in GitHub Actions, you may encounter pesky bugs with your production applications. For this reason, it's essential to use a good error monitoring tool that can help you quickly identify and fix issues. An excellent tool for this is Honeybadger, which can help you track, prioritize, and fix errors. A bonus of using Honeybadger is that it integrates seamlessly with Laravel, so you can get up and running quickly. I'd highly recommend checking it out so you can have peace of mind that your applications are running smoothly.
I am a freelance Laravel web developer who loves contributing to open-source projects, building exciting systems, and helping others learn about web development.