# Docker guide, build a PHP Application > Step by step guide showing how to build a simple application using Docker **Published by:** [anyvoid.eth](https://paragraph.com/@anyvoid.eth/) **Published on:** 2025-01-27 **Categories:** docker, php, docker-compose, devops **URL:** https://paragraph.com/@anyvoid.eth/docker-application-step-by-step-guide ## Content PrerequisitesIn this guide we will assume the following statements :docker cli is installed (required)you have a basic understanding of php (should have)you're familiar with dependency manager tool like composer (should have)Step 1 - Run php-fpm & nginx containers separatelyA PHP application is running over a web server. So first we have to select which php version to use. In this guide we will use php 8.4 with nginx as web server. Docker images can be pulled from Docker public registry and we can search for images using docker clidocker search phpMany images are listed, but we will use the official docker image :As we can see the name of the image is php without prefix. So we can access to the image using the url https://hub.docker.com/_/php. All available releases are listed in the tags tab.For this guide we will use 8.4.3-fpm-alpine3.20, so let's go and start the container!docker run php:8.4.3-fpm-alpine3.20Docker will first pull the image from the registry and next start php-fpmThe container is now ready to handle connections, it can be stopped by using Ctrl C. We can reproduce the same approach in order to find nginx image, we will use the latest available release :docker run nginxThe container start the server with multiple workers and it is ready to use. However at this point if you open your browser to http://localhost nothing will be displayed. In order to access the nginx welcome page, we have to forward the port 80, so first we have to stop the container using Ctrl C and next run the following command docker run -p 80:80 nginxTada 🥳 , we can now see the nginx welcome page when browsing to localhostStep 2 - Stronger togethers, display phpinfo()We are now able to start php and nginx containers, the next step will be to use them together and display a very simple index.php file. Let's go! Create index.php file src/index.php">mkdir src touch src/index.php echo -e " src/index.phpAdd nginx configurationmkdir -p docker/nginx touch docker/nginx/nginx.confnginx.conf file contentuser www-data; worker_processes 5; events { worker_connections 1024; } http { default_type application/octet-stream; charset utf-8; server_tokens off; tcp_nopush on; tcp_nodelay off; server { root /usr/share/nginx/html; location / { try_files $uri /index.php$is_args$args; } location ~ ^/(index)\.php(/|$) { fastcgi_pass php:9000; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $document_root; } } }Create a new docker network in order to allow communication between containersdocker network create app-networkStart php-fpmdocker run --rm --network app-network --name php -v $PWD/src:/usr/share/nginx/html php:8.4.3-fpm-alpine3.20Start nginxdocker run --rm --network app-network --name nginx -p 80:80 -v $PWD/docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro -v $PWD/src:/usr/share/nginx/html nginxResult on http://localhostStep 3 - Display data using PostgreSQL & DoctrineIn this step we will add a PostgreSQL database and use the Doctrine orm in order to manipulate entities. Doctrine can be installed by using composer, so we will init a new project and require dependencies :docker run --rm --interactive --tty --volume $PWD:/app composer initPackage name: docker/appDescription: leave emptyAuthor: leave emptyMinimum Stability: leave emptyPackage Type: leave emptyLicense: leave emptyWould you like to define dependencies: yesSearch for a package: doctrine/ormEnter the version constraint to require: leave emptySearch for a package: doctrine/dbalEnter the version constraint to require: leave emptySearch for a package: symfony/cacheEnter the version constraint to require: leave emptySearch for a package: leave emptyWould you like to define your dev dependencies: nAdd PSR-4 autoload mapping? Maps namespace "Docker\App" to the entered relative path. [src/, n to skip]: tap enterDo you confirm generation: yesWould you like to install dependencies now: yesComposer installation is done and we can now see the vendor directory, composer.json and composer.lock filesNow we can configure Doctrine. Create bootstrap.php filetouch bootstrap.php 'pdo_pgsql', 'user' => 'postgres', 'password' => 'secret', 'dbname' => 'postgres', 'host' => 'postgres', 'port' => 5432 ], $config); $entityManager = new EntityManager($connection, $config);"> 'pdo_pgsql', 'user' => 'postgres', 'password' => 'secret', 'dbname' => 'postgres', 'host' => 'postgres', 'port' => 5432 ], $config); $entityManager = new EntityManager($connection, $config);Create the bin/doctrine filemkdir bin touch bin/doctrine#!/usr/bin/env php id; } }We can now generate the schema with the following commanddocker run --rm --volume $PWD:/user/src/app php:8.4-cli php /user/src/app/bin/doctrine orm:schema-tool:update --force --dump-sqlThe following error should be displayedThis errors appears because pgsql driver is not installed in php:8.4-cli. In order to fix this error we will use a Dockerfile and add the extension, let's go!mkdir -p docker/php-cli touch docker/php-cli/DockerfileFROM php:8.4-cli RUN apt-get update && apt-get install -y \ libpq-dev \ && docker-php-ext-install pdo_pgsql pgsql \ && apt-get clean && rm -rf /var/lib/apt/lists/*Build a new image called my-php-clicd docker/php-cli docker build -t my-php:8.4-cli .We can now view the new imagedocker images | grep phpSo now we can try to create entities using our newest image my-php:8.4-clidocker run --rm --volume $PWD:/user/src/app my-php:8.4-cli php /user/src/app/bin/doctrine orm:schema-tool:update --force --dump-sqlOoops, we've got another errorIn ExceptionConverter.php line 77: An exception occurred in the driver: SQLSTATE[08006] [7] could not translat e host name "postgres" to address: Name or service not known In Exception.php line 24: SQLSTATE[08006] [7] could not translate host name "postgres" to address: Na me or service not known In PDOConnect.php line 25: SQLSTATE[08006] [7] could not translate host name "postgres" to address: Na me or service not known orm:schema-tool:update [--em EM] [--complete] [--dump-sql] [-f|--force]This error is about unkown host postgres, we have now to start the postgres container 🙂 Open a new terminal and run the following commanddocker run --rm --name postgres --network app-network -e POSTGRES_PASSWORD=secret postgresNext in another terminaldocker run --rm --network app-network --volume $PWD:/user/src/app my-php:8.4-cli php /user/src/app/bin/doctrine orm:schema-tool:update --force --dump-sqlSuccess!Updating database schema... CREATE TABLE albums(id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, title VARCHAR(255) NOT NULL, artist VARCHAR(255) NOT NULL, PRIMARY KEY(id)); 1 query was executed [OK] Database schema updated successfully!So here we go, we can now add some records in our new table 'albums', for that, we will use a simple php script.touch createAlbums.php 'Burn My Eyes', 'artist' => 'Machine Head' ], [ 'title' => 'Aggression Continuum', 'artist' => 'Fear Factory' ], [ 'title' => 'Black Album', 'artist' => 'Metallica' ] ]; foreach ($fixtures as $fixture) { $album = new Album( title: $fixture['title'], artist: $fixture['artist'] ); $entityManager->persist($album); $entityManager->flush(); echo "Created album with ID " . $album->getId() . "\n"; }"> 'Burn My Eyes', 'artist' => 'Machine Head' ], [ 'title' => 'Aggression Continuum', 'artist' => 'Fear Factory' ], [ 'title' => 'Black Album', 'artist' => 'Metallica' ] ]; foreach ($fixtures as $fixture) { $album = new Album( title: $fixture['title'], artist: $fixture['artist'] ); $entityManager->persist($album); $entityManager->flush(); echo "Created album with ID " . $album->getId() . "\n"; }docker run --rm --network app-network --volume $PWD:/user/src/app my-php:8.4-cli php /user/src/app/createAlbums.phpResultLast but not least, let's display the list! Create a new index.php file in the project root directorytouch index.phpgetRepository(Docker\App\BusinessRules\Entities\Album::class); $albums = $albumRepository->findAll(); $str = 'Albums'; $str .= ''; foreach ($albums as $album) { $str .= ''.$album->title.' ('.$album->artist.')'; } $str .= ''; echo $str;">getRepository(Docker\App\BusinessRules\Entities\Album::class); $albums = $albumRepository->findAll(); $str = '

Albums

'; $str .= ''; echo $str;Add pgsql extension in php-fpm containermkdir -p docker/php-fpm touch docker/php-fpm/DockerfileFROM php:8.4.3-fpm-alpine3.20 RUN apk add --no-cache \ postgresql-dev \ && docker-php-ext-install pdo_pgsql pgsqlBuild the imagecd docker/php-fpm docker build -t my-php:8.4.3-fpm-alpine3.20 .Start containers (in separate terminals) Start postgresdocker run --rm --name postgres --network app-network -e POSTGRES_PASSWORD=secret postgresStart php-fpmdocker run --rm --network app-network --name php -v $PWD:/usr/share/nginx/html my-php:8.4.3-fpm-alpine3.20Start nginxdocker run --rm --network app-network --name nginx -p 80:80 -v $PWD/docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro -v $PWD:/usr/share/nginx/html nginxResultCongratulations! This simple page is displayed using 3 containers, we can see them with this commandWe can now stop all containers by using Ctrl C in each terminal.Step 4 - Docker ComposeIn this last step we will introduce docker-compose in order to make our life easier 🙂 Instead of running each container one by one we will create a special file in order to manage them all in one place. Let's go!touch docker-compose.ymlnetworks: app-network: services: postgres: image: postgres:latest container_name: postgres environment: POSTGRES_PASSWORD: secret networks: - app-network php-fpm: build: ./docker/php-fpm image: my-php:8.4.3-fpm-alpine3.20 container_name: php working_dir: /usr/share/nginx/html volumes: - .:/usr/share/nginx/html networks: - app-network nginx: image: nginx:latest container_name: nginx working_dir: /usr/share/nginx/html volumes: - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - .:/usr/share/nginx/html ports: - 80:80 networks: - app-network php-cli: build: ./docker/php-cli image: my-php:8.4-cli container_name: php-cli volumes: - .:/user/src/app networks: - app-network working_dir: /user/src/app composer: image: composer:latest container_name: composer volumes: - .:/appInit databasedocker-compose run --rm php-cli bin/doctrine orm:schema-tool:update --force --dump-sqlAdd recordsdocker-compose run --rm php-cli php createAlbums.phpStart containersdocker-compose up -dAdd a new dev dependency using composerdocker-compose run --rm composer require --dev phpunit/phpunitStop containers and remove all volumesdocker-compose down -vThat's it! As we can see all settings have been moved into docker-compose.yml, command are becoming more simple and it allow to start/stop containers in a breeze! Resourceshttps://hub.docker.comhttps://docker.comhttps://getcomposer.org/https://www.doctrine-project.org/projects/doctrine-orm/en/current/tutorials/getting-started.htmlhttps://docs.docker.com/compose/Thank you for taking the time to read this article and congratulations for running this sample application! Feel free to share your thoughts, experiences, or questions in the comments. ## Publication Information - [anyvoid.eth](https://paragraph.com/@anyvoid.eth/): Publication homepage - [All Posts](https://paragraph.com/@anyvoid.eth/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@anyvoid.eth): Subscribe to updates ## Optional - [Collect as NFT](https://paragraph.com/@anyvoid.eth/docker-application-step-by-step-guide): Support the author by collecting this post - [View Collectors](https://paragraph.com/@anyvoid.eth/docker-application-step-by-step-guide/collectors): See who has collected this post