The Compose file
Now let's connect the app image from
the previous lesson to a MySQL database. In your
project root, create docker-compose.yml:
services:
app:
build: .
ports:
- "8000:8000"
environment:
DB_HOST: db
DB_DATABASE: ${DB_NAME}
DB_USERNAME: root
DB_PASSWORD: ${DB_PASSWORD}
depends_on:
- db
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
volumes:
- db-data:/var/lib/mysql
volumes:
db-data:
Everything here comes from the Compose chapter:
-
appusesbuild: .to build from our Dockerfile. -
portspublishes port 8000 so you can open the app in your browser. -
environmentonapptells Laravel how to reach the database. NoticeDB_HOST: db- that's the service name of the database, thanks to Compose's automatic name-based networking.
-
dbruns MySQL and stores its data in thedb-datavolume, so it survives restarts. -
depends_onstarts the database before the app.
The .env file
Create a .env file next to the Compose file for the secrets it references:
DB_NAME=app
DB_PASSWORD=secret
Starting the whole app
Build and start everything with one command:
docker compose up -d --build
Compose builds your app image, starts MySQL, creates the network and volume, and
launches both containers. Open http://localhost:8000 to see your app.
Run artisan commands with compose exec
You'll often need to run commands inside the app container - like database
migrations. Use docker compose exec (the Compose version of docker exec):
docker compose exec app php artisan migrate
This runs php artisan migrate inside the running app container, which can reach
the database at host db.
Tearing down
Stop and remove everything when you're done:
docker compose down
You've now dockerized a real PHP/Laravel app with a database - using only the pieces this course taught. The final chapter covers practices that make your images smaller, faster and safer.
The migration timing gotcha
If you run migrations too early, they fail with a "connection refused" error - not
because your config is wrong, but because MySQL needs a few seconds to be ready after
its container starts, and depends_on doesn't wait for that. If docker compose exec app php artisan migrate fails right after up, give the database a moment and run it
again. In real projects people add a small wait-or-retry step so this happens
automatically.
FAQ
How do I run artisan (or other) commands inside a container?
Use docker compose exec <service> <command>, for example docker compose exec app php artisan migrate. It runs the command inside the already-running service container,
which can reach the database by its service name.
Why does php artisan migrate fail right after docker compose up?
The database container has started but MySQL isn't ready for connections yet. Wait a few seconds and rerun the migration, or add a wait/retry step so the app doesn't connect too early.
What host should Laravel use to reach the database in Compose?
The database service name from your Compose file (here db), not localhost. Inside
a container, localhost points at that same container, so the service name is what
reaches MySQL.