Use containers for PHP development
Prerequisites
Complete Containerize a PHP application.
Overview
In this section, you'll learn how to set up a development environment for your containerized application. This includes:
- Adding a local database and persisting data
- Adding phpMyAdmin to interact with the database
- Configuring Compose to automatically update your running Compose services as you edit and save your code
- Creating a development container that contains the dev dependencies
Add a local database and persist data
You can use containers to set up local services, like a database. To do this for the sample application, you'll need to do the following:
- Update the
Dockerfile
to install extensions to connect to the database - Update the
compose.yaml
file to add a database service and volume to persist data
Update the Dockerfile to install extensions
To install PHP extensions, you need to update the Dockerfile
. Open your
Dockerfile in an IDE or text editor and then update the contents. The following
Dockerfile
includes one new line that installs the pdo
and pdo_mysql
extensions. All comments have been removed.
# syntax=docker/dockerfile:1
FROM composer:lts as deps
WORKDIR /app
RUN --mount=type=bind,source=composer.json,target=composer.json \
--mount=type=bind,source=composer.lock,target=composer.lock \
--mount=type=cache,target=/tmp/cache \
composer install --no-dev --no-interaction
FROM php:8.2-apache as final
RUN docker-php-ext-install pdo pdo_mysql
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --from=deps app/vendor/ /var/www/html/vendor
COPY ./src /var/www/html
USER www-data
For more details about installing PHP extensions, see the Official Docker Image for PHP.
Update the compose.yaml file to add a db and persist data
Open the compose.yaml
file in an IDE or text editor. You'll notice it
already contains commented-out instructions for a PostgreSQL database and volume. For this application, you'll use MariaDB. For more details about MariaDB, see the
MariaDB Official Docker image.
Open the src/database.php
file in an IDE or text editor. You'll notice that it reads environment variables in order to connect to the database.
In the compose.yaml
file, you'll need to update the following:
- Uncomment and update the database instructions for MariaDB.
- Add a secret to the server service to pass in the database password.
- Add the database connection environment variables to the server service.
- Uncomment the volume instructions to persist data.
The following is the updated compose.yaml
file. All comments have been removed.
services:
server:
build:
context: .
ports:
- 9000:80
depends_on:
db:
condition: service_healthy
secrets:
- db-password
environment:
- PASSWORD_FILE_PATH=/run/secrets/db-password
- DB_HOST=db
- DB_NAME=example
- DB_USER=root
db:
image: mariadb
restart: always
user: root
secrets:
- db-password
volumes:
- db-data:/var/lib/mysql
environment:
- MARIADB_ROOT_PASSWORD_FILE=/run/secrets/db-password
- MARIADB_DATABASE=example
expose:
- 3306
healthcheck:
test:
[
"CMD",
"/usr/local/bin/healthcheck.sh",
"--su-mysql",
"--connect",
"--innodb_initialized",
]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:
secrets:
db-password:
file: db/password.txt
Note
To learn more about the instructions in the Compose file, see Compose file reference.
Before you run the application using Compose, notice that this Compose file uses
secrets
and specifies a password.txt
file to hold the database's password.
You must create this file as it's not included in the source repository.
In the docker-php-sample
directory, create a new directory named db
and
inside that directory create a file named password.txt
. Open password.txt
in an IDE or text editor and add the following password. The password must be on a single line, with no additional lines in the file.
example
Save and close the password.txt
file.
You should now have the following in your docker-php-sample
directory.
├── docker-php-sample/
│ ├── .git/
│ ├── db/
│ │ └── password.txt
│ ├── src/
│ ├── tests/
│ ├── .dockerignore
│ ├── .gitignore
│ ├── compose.yaml
│ ├── composer.json
│ ├── composer.lock
│ ├── Dockerfile
│ ├── README.Docker.md
│ └── README.md
Run the following command to start your application.
$ docker compose up --build
Open a browser and view the application at http://localhost:9000/database.php. You should see a simple web application with text and a counter that increments every time you refresh.
Press ctrl+c
in the terminal to stop your application.
Verify that data persists in the database
In the terminal, run docker compose rm
to remove your containers and then run docker compose up
to run your application again.
$ docker compose rm
$ docker compose up --build
Refresh http://localhost:9000/database.php in your browser and verify that the previous count still exists. Without a volume, the database data wouldn't persist after you remove the container.
Press ctrl+c
in the terminal to stop your application.
Add phpMyAdmin to interact with the database
You can easily add services to your application stack by updating the compose.yaml
file.
Update your compose.yaml
to add a new service for phpMyAdmin. For more details, see the
phpMyAdmin Official Docker Image. The following is the updated compose.yaml
file.
services:
server:
build:
context: .
ports:
- 9000:80
depends_on:
db:
condition: service_healthy
secrets:
- db-password
environment:
- PASSWORD_FILE_PATH=/run/secrets/db-password
- DB_HOST=db
- DB_NAME=example
- DB_USER=root
db:
image: mariadb
restart: always
user: root
secrets:
- db-password
volumes:
- db-data:/var/lib/mysql
environment:
- MARIADB_ROOT_PASSWORD_FILE=/run/secrets/db-password
- MARIADB_DATABASE=example
expose:
- 3306
healthcheck:
test:
[
"CMD",
"/usr/local/bin/healthcheck.sh",
"--su-mysql",
"--connect",
"--innodb_initialized",
]
interval: 10s
timeout: 5s
retries: 5
phpmyadmin:
image: phpmyadmin
ports:
- 8080:80
depends_on:
- db
environment:
- PMA_HOST=db
volumes:
db-data:
secrets:
db-password:
file: db/password.txt
In the terminal, run docker compose up
to run your application again.
$ docker compose up --build
Open
http://localhost:8080 in your browser to access phpMyAdmin. Log in using root
as the username and example
as the password. You can now interact with the database through phpMyAdmin.
Press ctrl+c
in the terminal to stop your application.
Automatically update services
Use Compose Watch to automatically update your running Compose services as you edit and save your code. For more details about Compose Watch, see Use Compose Watch.
Open your compose.yaml
file in an IDE or text editor and then add the Compose Watch instructions. The following is the updated compose.yaml
file.
services:
server:
build:
context: .
ports:
- 9000:80
depends_on:
db:
condition: service_healthy
secrets:
- db-password
environment:
- PASSWORD_FILE_PATH=/run/secrets/db-password
- DB_HOST=db
- DB_NAME=example
- DB_USER=root
develop:
watch:
- action: sync
path: ./src
target: /var/www/html
db:
image: mariadb
restart: always
user: root
secrets:
- db-password
volumes:
- db-data:/var/lib/mysql
environment:
- MARIADB_ROOT_PASSWORD_FILE=/run/secrets/db-password
- MARIADB_DATABASE=example
expose:
- 3306
healthcheck:
test:
[
"CMD",
"/usr/local/bin/healthcheck.sh",
"--su-mysql",
"--connect",
"--innodb_initialized",
]
interval: 10s
timeout: 5s
retries: 5
phpmyadmin:
image: phpmyadmin
ports:
- 8080:80
depends_on:
- db
environment:
- PMA_HOST=db
volumes:
db-data:
secrets:
db-password:
file: db/password.txt
Run the following command to run your application with Compose Watch.
$ docker compose watch
Open a browser and verify that the application is running at http://localhost:9000/hello.php.
Any changes to the application's source files on your local machine will now be immediately reflected in the running container.
Open hello.php
in an IDE or text editor and update the string Hello, world!
to Hello, Docker!
.
Save the changes to hello.php
and then wait a few seconds for the application to sync. Refresh
http://localhost:9000/hello.php in your browser and verify that the updated text appears.
Press ctrl+c
in the terminal to stop Compose Watch. Run docker compose down
in the terminal to stop the application.
Create a development container
At this point, when you run your containerized application, Composer isn't installing the dev dependencies. While this small image is good for production, it lacks the tools and dependencies you may need when developing and it doesn't include the tests
directory. You can use multi-stage builds to build stages for both development and production in the same Dockerfile. For more details, see
Multi-stage builds.
In the Dockerfile
, you'll need to update the following:
- Split the
deps
staged into two stages. One stage for production (prod-deps
) and one stage (dev-deps
) to install development dependencies. - Create a common
base
stage. - Create a new
development
stage for development. - Update the
final
stage to copy dependencies from the newprod-deps
stage.
The following is the Dockerfile
before and after the changes.
# syntax=docker/dockerfile:1
FROM composer:lts as deps
WORKDIR /app
RUN --mount=type=bind,source=composer.json,target=composer.json \
--mount=type=bind,source=composer.lock,target=composer.lock \
--mount=type=cache,target=/tmp/cache \
composer install --no-dev --no-interaction
FROM php:8.2-apache as final
RUN docker-php-ext-install pdo pdo_mysql
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --from=deps app/vendor/ /var/www/html/vendor
COPY ./src /var/www/html
USER www-data
# syntax=docker/dockerfile:1
FROM composer:lts as prod-deps
WORKDIR /app
RUN --mount=type=bind,source=./composer.json,target=composer.json \
--mount=type=bind,source=./composer.lock,target=composer.lock \
--mount=type=cache,target=/tmp/cache \
composer install --no-dev --no-interaction
FROM composer:lts as dev-deps
WORKDIR /app
RUN --mount=type=bind,source=./composer.json,target=composer.json \
--mount=type=bind,source=./composer.lock,target=composer.lock \
--mount=type=cache,target=/tmp/cache \
composer install --no-interaction
FROM php:8.2-apache as base
RUN docker-php-ext-install pdo pdo_mysql
COPY ./src /var/www/html
FROM base as development
COPY ./tests /var/www/html/tests
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
COPY --from=dev-deps app/vendor/ /var/www/html/vendor
FROM base as final
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --from=prod-deps app/vendor/ /var/www/html/vendor
USER www-data
Update your compose.yaml
file by adding an instruction to target the
development stage.
The following is the updated section of the compose.yaml
file.
services:
server:
build:
context: .
target: development
# ...
Your containerized application will now install the dev dependencies.
Run the following command to start your application.
$ docker compose up --build
Open a browser and view the application at http://localhost:9000/hello.php. You should still see the simple "Hello, Docker!" application.
Press ctrl+c
in the terminal to stop your application.
While the application appears the same, you can now make use of the dev dependencies. Continue to the next section to learn how you can run tests using Docker.
Summary
In this section, you took a look at setting up your Compose file to add a local database and persist data. You also learned how to use Compose Watch to automatically sync your application when you update your code. And finally, you learned how to create a development container that contains the dependencies needed for development.
Related information:
Next steps
In the next section, you'll learn how to run unit tests using Docker.