До тук създадохме контейнер nginx чрез Docker. Накарахме контейнера да взема папки и файлове принадлежащи на хоста. Сега ще се над градим използвайки PHP. Той ще е нужен за създаване на различни скриптове и връзка с база данни. Теоретично бихме могли да ползваме някой готов образ с PHP, но той идва толкова "орязан", че се губи смисъла от използването му. Ще изградим наш образ и ще го оптимизираме. За създаването му се нуждаем от файл в котйто ще се опишат всички зависимости и нужни компоненти.
sudo nano ~/docker-project/images/php/Dockerfile FROM php:8.1-fpm RUN apt-get update RUN apt-get install -y curl git RUN docker-php-ext-install pdo pdo_mysql WORKDIR /var/www/public_html ENTRYPOINT [ "php-fpm" ]
FROM - дефинираме базов образ който ще ползваме за създаване на наш. RUN - команда която казва да се изпълни това което е след нея RUN apt-get update - обновяваме хранилищата в контейнера RUN apt-get install -y curl git - инсталираме библиотеките curl и git в контейнера RUN docker-php-ext-install pdo pdo_mysql - инсталира в контейнера pdo и pdo_mysql WORKDIR /var/www/public_html - за удобство на работа с контейнера създаваме работна директория извън контейнера (в хоста) ENTRYPOINT [ "php-fpm" ] - менажер на процеса php-fpm. Тази команда ще се стартира веднага след като стартира контейнера. Едно лирично отклонение! Аналогично на ENTRYPOINT е и командата CMD. Тя също изпълнява команди след като контейнера е стартирал. ENTRYPOINT винаги се изпълнява преди CMD. Има обаче важна разлика при ползването им: ENTRYPOINT [ "php-fpm", "-c" ] - изпълнява само php-fpm, -c се явява опция на командата. CMD [ "/var/www" ] - изпълнява /var/www, като презаписва върху общата команда. За пояснение, стартирате контейнера със следните параметри: docker run -d nginx /var/www2 - използвайки ENTRYPOINT, командата няма да се промени - използвайки CMD [ "/var/www" ], командата ще се промени на: docker run -d nginx /var/www
docker build -t php81fpm:1.0 ~/docker-project/images/php/
Образа ни ще се казва php81fpm, а версията му ще е 1.0. ~/docker-project/images/php/ е пътя където се намира Dockerfile.
Да проверим какви образи имаме.
docker images REPOSITORY TAG IMAGE ID CREATED SIZE php81fpm 1.0 322fa8d58f42 38 seconds ago 558MB nginx latest 5cdef4ac3335 4 days ago 161MB
Тук създадохме образ с версия 1.0. Сега да създадем още един такъв с версия latest, за по-лесна употреба.
docker build -t php81fpm ~/docker-project/images/php/ docker images REPOSITORY TAG IMAGE ID CREATED SIZE php81fpm 1.0 322fa8d58f42 3 minutes ago 558MB php81fpm latest 322fa8d58f42 3 minutes ago 558MB nginx latest 5cdef4ac3335 4 days ago 161MB
Дайте сега да стартираме нашия контейнер.
docker run -d -v ~/docker-project/www:/var/www/public_html php81fpm e7d8b6cb8f9c7146b462f158f1786b16431a1dac6df8fabcef9d9c851460b59c
Контейнера стартира. Да проверим какви стартирали контейнери имаме.
docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e7d8b6cb8f9c php81fpm "php-fpm" About a minute ago Up About a minute 9000/tcp nifty_einstein a55e0681fe7d nginx "/docker-entrypoint.…" About an hour ago Up About an hour 0.0.0.0:80->80/tcp, :::80->80/tcp clever_haslett
Обърнете внимание! Нашия контейнер php81fpm работи на порт 9000. Имаме още един контейнер, nginx работещ на порт 80. Задачата е, контейнера nginx да се обръща към php81fpm, като му предава заявки за изпълнение на PHP скриптове, след което да получи резултата от работата им. Накрая резултата да се получи в браузъра на клиента. До тук знаем, че ползваме порт 9000 за php81fpm. Нужен ни е IP адреса на контейнера.
docker inspect e7d8b6cb8f9c | grep IPAddress "SecondaryIPAddresses": null, "IPAddress": "172.17.0.3", "IPAddress": "172.17.0.3",
Контейнера работи на IP адрес 172.17.0.3 и порт 9000. За да работи NGINX като прокси на PHP-FPM трябва да се редактира конфигурационния файл на виртуалния хост.
sudo nano ~/docker-project/vhost.conf server { listen 80; server_name localhost; index index.php; root /var/www/public_html; location ~ \.php$ { try_files $uri =404; fastcgi_pass 172.17.0.3:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }
index.php - променяме индексния файл location ~ \.php$ - указваме мястото където да се търси PHP скрипта. Същевременно с .php$ казваме адреса да завършва на .php try_files $uri =404; - проверяваме за наличие на php скрипт, ако няма такъв, препраща към грешка 404 fastcgi_pass 172.17.0.3:9000; - дефинираме NGINX по какъв адрес и порт да търси PHP скрипта, това е IP и адреса на контейнера php81fpm fastcgi_index index.php; - дефинираме как ще се казва индексния файл include fastcgi_params; - казваме да се включи файл със стандартни параметри fastcgi_param fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - допълваме fastcgi_param с допълнителен параметър fastcgi_script_name Конфигурационният файл е готов. За да се възприеме трябва да рестартираме NGINX вътре в контейнера.
docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e7d8b6cb8f9c php81fpm "php-fpm" 36 minutes ago Up 36 minutes 9000/tcp nifty_einstein a55e0681fe7d nginx "/docker-entrypoint.…" 2 hours ago Up 2 hours 0.0.0.0:80->80/tcp, :::80->80/tcp clever_haslett docker exec a55e0681fe7d nginx -s reload 2026/02/10 11:58:06 [notice] 36#36: signal process started
Да проверим как работи PHP. Първо създаваме такъв файл за тест.
sudo nano ~/docker-project/www/index.php <?php phpinfo();
Да отворим страницата в браузъра: http://192.168.11.44/. Ще върне информация за наличния PHP (версия, плъгини и т.н.).
До тук всичко работи. Проблем е, че преди всяко пускане на контейнера трябва да гледаме php81fpm какъв IP адрес има. След това трябва всеки път да променяме конфигурационния файл. За да се премахне горното неудобство трябва двата контейнера nginx и php81fpm да работят в отделна, изолирана мрежа и да си комуникират през нея. Така няма да се налага да следим IP адреси, а ще работим с имена на контейнери. Не знам дали обърнахте внимание, но контейнерите до сега ползваха вътрешна мрежа, различна от тая на хоста която го свързва с Интернет. В нашия случай мрежата е 172.17.0.0. Имайки предвид всичко казано до тук ще създадем изолирана вътрешна мрежа в която ще работят ngnix и php81fpm.
docker network create network1 be25d1193ad171f8d177e550a47441c822d3b9a558d1a19f9a89ad4135d0677f
Да проверим какви мрежи имаме.
docker network ls NETWORK ID NAME DRIVER SCOPE ff7402d7215a bridge bridge local 4a9997b44d73 host host local be25d1193ad1 network1 bridge local 4de4d757d9c4 none null local
bridge - това е мрежата която се създаде по подразбиране с инсталацията на Docker, тя работи като мрежови мост. Благодарение на тази мрежа контейнерите могат да кореспондират помежду си, знаеки IP адресите им. network1 - това е мрежата която създадохме и тя работи като мрежови мост host - това е мрежа обединяваща контейнерите и хостовата операционна система. Тя е уникална и не може да има друга подобна. Именно в тази мрежа става прехвърлянето на портове в контейнера. none - това е мрежа null. Тя е необходима за пълна изолация на контейнера. В тази мрежа контейнерите не се виждат един друг и не се свързват с хоста чрез прехвърляне на портове. От тук нататък трябва да изпълним две неща: - да стартираме контейнера в изолираната мрежа - да дадем ново име на нашия контейнер за да боравим с него, а не с IP За начало да спрем всички контейнери.
Можем да видим всички стартирали контейнери във вид на списък.
docker ps -q e7d8b6cb8f9c a55e0681fe7d
Да спрем всички контейнери.
docker stop $(docker ps -q) e7d8b6cb8f9c a55e0681fe7d docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
За да стартираме контейнера в новосъздадената мрежа с точно определено име. Започваме с nginx.
docker run -d -p 80:80 -v ~/docker-project/vhost.conf:/etc/nginx/conf.d/default.conf -v ~/docker-project/www:/var/www/public_html --network network1 --name nginx1 nginx 7dbab95e76480d93cb8cbc6a1ae1475303063984406d601b9768a60f19a05fa4
Освен NGINX трябва да стартираме и другия контейнер, а именно php81fpm.
docker run -d -v ~/docker-project/www:/var/www/public_html --network network1 --name php1 php81fpm a6a676f8a5930b3364b617fc42497f3bb21e90d34b6e796f537f440e30b37bee docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a6a676f8a593 php81fpm "php-fpm" 22 seconds ago Up 22 seconds 9000/tcp php1 7dbab95e7648 nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp nginx1
Забележете, двата контейнера работят в изолиран частен мрежови мост наречен network1. Освен това вместо IP адрес използват имената си, съответно nginx1 и php1. За да завършим текущата конфигурация е необходимо да поправим vhost.conf.
sudo nano ~/docker-project/vhost.conf server { listen 80; server_name localhost; index index.php; root /var/www/public_html; location ~ \.php$ { try_files $uri =404; fastcgi_pass php1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }
След тази промяна е необходимо да се рестартира NGINX.
docker exec nginx1 nginx -s reload 2026/02/10 17:03:46 [notice] 36#36: signal process started
Обновяваме страницата http://192.168.11.44/. Всичко работи нормално.
До тук всичко върви по мед и масло. Нямаме грешки всичко се стартира. Винаги когато стартираме docker run се стартира нов контейнер. Представете си сега, че сте създали нов контейнер и искате да го пуснете със съществуващо име (вече използвано). За да се получи примера по-интересен първо ще спрем всички контейнери.
docker stop $(docker ps -q)
Сега да се пробваме да създадем контейнер с NGINX със същия ред който го създавахме до сега.
docker run -d -p 80:80 -v ~/docker-project/vhost.conf:/etc/nginx/conf.d/default.conf -v ~/docker-project/www:/var/www/public_html --network network1 --name nginx1 nginx docker: Error response from daemon: Conflict. The container name "/nginx1" is already in use by container "7dbab95e76480d93cb8cbc6a1ae1475303063984406d601b9768a60f19a05fa4". You have to remove (or rename) that container to be able to reuse that name. See 'docker run --help'.
Връща грешка. Контейнера nginx1 се използва в друг контейнер. Независимо, че нямаме пуснт контейнер с такова име. Дайте да проверим наистина ли е така?
docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a6a676f8a593 php81fpm "php-fpm" 24 minutes ago Exited (0) 8 minutes ago php1 7dbab95e7648 nginx "/docker-entrypoint.…" 27 minutes ago Exited (0) 8 minutes ago nginx1 308fe56e74b1 php81fpm "php-fpm" 31 minutes ago Exited (0) 27 minutes ago wizardly_hofstadter 296e4784a8eb nginx "/docker-entrypoint.…" 35 minutes ago Exited (0) 27 minutes ago boring_jennings
Наистина имаме такъв контейнер използващ името nginx1, макар и спрян. Ако искате да пуснете съществуващ спрян контейнер, без да правите промени по него то:
docker start nginx1 php1 nginx1 php1
За по-голямо разнообразие стартирахме едновременно два контейнера с една команда. Да обновим страницата http://192.168.11.44/. Всичко работи нормално. По-сложна задача е когато искате да направите промени по контейнера и наново да го стартирате под същото име.
За да се изпълни горното условие е нужно контейнера първо да се изтрие и след това да се пресъздаде контейнер със същото име.
docker rm nginx1 php1 Error response from daemon: cannot remove container "/nginx1": container is running: stop the container before removing or force remove Error response from daemon: cannot remove container "/php1": container is running: stop the container before removing or force remove
Няма да можем да ги изтрием защото контейнерите са стартирали. Можем да ги изтрием след като ги спрем. Другия вариант за изтриване без да спираме контейнерите е:
docker rm -f nginx1 php1 nginx1 php1
rm - дефинира контейнера да се изтрие (remove) -f - контейнера принудително да се изтрие (force)
docker run -d -p 80:80 -v ~/docker-project/vhost.conf:/etc/nginx/conf.d/default.conf -v ~/docker-project/www:/var/www/public_html --network network1 --name nginx1 nginx docker run -d -v ~/docker-project/www:/var/www/public_html --network network1 --name php1 php81fpm 5384d098b6953fb11e9bc203698cd8cf5a507557288597ee35ec9de76eca5f25 8fa044d5332ae10005a796d4c01316d82d9aa5ddd26fb64f3bf0b80fc3aebe3f
Контейнерите се стартираха без грешки и конфликти. Да проверим сайта как работи на адрес http://192.168.11.44/. Сайта не работи. Да проверим дали контейнерите са стартирали?
docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8fa044d5332a php81fpm "php-fpm" 3 minutes ago Up 3 minutes 9000/tcp php1
Имаме стартирал само един контейнер, а именно php1. Контейнера nginx1 не е стартирал. Да проверим дали съществува.
docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8fa044d5332a php81fpm "php-fpm" 3 minutes ago Up 3 minutes 9000/tcp php1 cccp@debian:~$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8fa044d5332a php81fpm "php-fpm" 5 minutes ago Up 5 minutes 9000/tcp php1 5384d098b695 nginx "/docker-entrypoint.…" 5 minutes ago Exited (1) 5 minutes ago nginx1 308fe56e74b1 php81fpm "php-fpm" 51 minutes ago Exited (0) 47 minutes ago wizardly_hofstadter 296e4784a8eb nginx "/docker-entrypoint.…" 55 minutes ago Exited (0) 47 minutes ago boring_jennings
Има такъв наличен но не е стартирал. След Exited имаме (1). Това означава, че не е стартирал поради някаква грешка вътре в кнтейнера.
Дайте да видим логовете вътре в контейнера за да определим грешката.
docker logs nginx1 /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf 10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf differs from the packaged version /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh /docker-entrypoint.sh: Configuration complete; ready for start up 2026/02/10 17:32:43 [emerg] 1#1: host not found in upstream "php1" in /etc/nginx/conf.d/default.conf:8 nginx: [emerg] host not found in upstream "php1" in /etc/nginx/conf.d/default.conf:8
Предупреждава, че когато се опитва да стартира контейнера във файла за виртуалните хостове, не съществува php1. Наистина първо стартирахме контейнера nginx1, а чак след това php1. Това означава, че във виртуалния хост няма как да съществува php1. Дайте да спрем всички контейнери. След това да ги стартираме в правилно подреждане.
docker stop $(docker ps -q) docker start php1 nginx1 php1 nginx1
Сега всичко стартира правилно. Проверяваме сайта http://192.168.11.44/. Всичко върви нормално.
До тук схемата на взаимодействие е: (localhost:80) <=> (nginx 0.0.0.0:80) <=> (php-fpm 0.0.0.0:9000) Хоста на 80 порт се обръща към контейнера nginx. Той обработва заявката и се обръща към PHP като прокси. А сега една малко по-сложна схема. (localhost:80) <=> (nginx 0.0.0.0:80) <=> (php-fpm 0.0.0.0:9000) <╗ ║ (localhost:1500) <=> (phpmyadmin 0.0.0.0:80) <=> (mysql 0.0.0.0:3306) <╝ MySQL ще комуникира едновременно с PHPMyAdmin и NGINX. Новата постановка ще я разгледаме в следващата задача.