Integration tests need a real database, and a hosted runner is an empty machine — which is the problem service containers exist for.
jobs:
test:
runs-on: ubuntu-20.04
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: app_test
ports: ['3306:3306']
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-retries=5
The health options are not optional: without them the job starts before MySQL is listening, and the failure is an intermittent connection refused that only happens on a cold runner. Ports are published to the host, so the application connects to 127.0.0.1 rather than to a service name — which is the difference from a compose file and catches everyone once. A container image with an entrypoint that needs arguments cannot be a service, which is the main limitation.