W tym wpisie wyjaśnię jak utworzyć kontenery dla aplikacji WordPress, działającej za serwerem Nginx reverse proxy z domeną zabezpieczoną certyfikatem SSL. Kontenery dla serwisów będą utworzone przy pomocy Dockera, a konfiguracja i uruchamianie kontenerów będzie możliwe przy pomocy Docker Compose. Infrastruktura składa się z serwisów: WordPress (CMS), MySQL (baza danych), Nginx (reverse-proxy) oraz Certbot (generowanie certyfikatów SSL). Komunikacja client-server będzie przebiegać za pośrednictwem protokołu SSL. Żądanie trafia do serwera reverse proxy skonfigurowanego dla danej domeny, a następnie jest przekierowywane do kontenera aplikacji WordPress. Zdefiniowanie sieci typu bridge w pliku Docker Compose pozwoli na komunikacje pomiędzy kontenerami korzystając z wewnętrznego DNSa.
Aby w pełni skorzystać z tutoriala wymagane jest posiadanie domeny oraz serwera z publicznym adresem IP. Należy utworzyć rekordy DNS u dostawcy domeny wskazujące na serwer reverse proxy i mieć dostęp do infrastruktury sieciowej w celu otwarcia portów serwera sieciowego.
Na początek pobierz repozytorium a dalszej części wyjaśnię konfiguracje dla poszczególnych kontenerów.
$ git clone https://github.com/it-stuff-pl/guides.gitKontenery wymagają dostępu do określonych zmiennych środowiskowych w czasie wykonywania. Zmienne te obejmują zarówno informacje poufne takie jak: hasła dla administratora oraz użytkowników MySQL, a także niepoufne takie jak nazwa bazy danych czy hosta. Zmienne zostały wskazane w pliku docker-compose.yaml (plik konfiguracyjny dla kontenerów). Domyślnie to plik .env jest używany przez Docker Compose w celu pozyskania wartości wskazanych zmiennych. Utwórz plik i uzupełnij o hasło dla użytkownika i administratora bazy danych.
MYSQL_DB=wordpress
MYSQL_USER=wp-user
MYSQL_PASSWORD=user_password
MYSQL_ROOT_PASSWORD=admin_password
MYSQL_HOST=db:3306Następnie zwróć uwagę na konfiguracje dla Nginx-a, która została podzielona na dwa pliki nginx.conf i nginx-ssl.conf.
## Pamiętaj o wpisaniu swojej nazwy domeny w blokach server
server {
listen 80;
listen [::]:80;
server_name nazwa_domeny www.nazwa_domeny;
index index.php index.html index.htm;
root /var/www/html;
location ~ /.well-known/acme-challenge {
allow all;
root /var/www/html;
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location ~ /\.ht {
deny all;
}
location = /favicon.ico {
log_not_found off; access_log off;
}
location = /robots.txt {
log_not_found off; access_log off; allow all;
}
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
expires max;
log_not_found off;
}
}Pierwszy plik nginx.conf zawiera blok serwera HTTP a w nim dyrektywy:
- listen – Port na którym Nginx nasłuchuje w celu obsługi żądań
- server_name – Definiuje nazwę serwera i blok który powinien być używany do obsługi żądań. W tym miejscu należy wpisać adres twojej domeny.
- index – Określa który plik będzie przetwarzany i wyświetlany po wysłaniu żądania przez klienta na określony adres.
- root – Definiuje lokalizacje używaną do wyszukiwania żądanych plików.
- location – Określa jak obsługiwane są żądania w zależności od adresu URL. Zawiera bloki lokacji odpowiedzialne za:
- Obsługę żądań dla Certbota, który weryfikuje czy DNS twojej domeny wskazuje na poprawny serwer poprzez wysłanie żądania do serwerów Let’s Encrypt.
- Przetwarzanie żądań PHP i przekierowanie ich do kontenera WordPress, korzystając z protokołu FastCGI.
- Wyłączenie access logów dla określonych lokacji
- Pamięć cache dla plików statycznych po stronie przeglądarki klienta.
Drugi plik nginx-ssl.conf, będzie używany zamiast nginx.conf po poprawnym wygenerowaniu certyfikatów testowych. Konfiguracja drugiego pliku obejmuje przekierowania z adresu HTTP na HTTPS i lokalizacje dla certyfikatu i klucza SSL. Wszystkie dyrektywy location dla WordPressa zostały przeniesione do bloku HTTPS.
## Pamiętaj o wpisaniu swojej nazwy domeny w blokach server
## oraz w ssl_certificate i ssl_certificate_key
server {
listen 80;
listen [::]:80;
server_name nazwa_domeny www.nazwa_domeny;
location ~ /.well-known/acme-challenge {
allow all;
root /var/www/html;
}
location / {
rewrite ^ https://$host$request_uri? permanent;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name nazwa_domeny www.nazwa_domeny;
index index.php index.html index.htm;
root /var/www/html;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/nazwa_domeny/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/nazwa_domeny/privkey.pem;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location ~ /\.ht {
deny all;
}
location = /favicon.ico {
log_not_found off; access_log off;
}
location = /robots.txt {
log_not_found off; access_log off; allow all;
}
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
expires max;
log_not_found off;
}
}Do uruchomienia i określenie parametrów dla wszystkich kontenerów zostanie wykorzystane narzędzie Docker Compose. Jest to narzędzie ułatwiające uruchamianie i zarządzanie kontenerami, dające możliwość tworzenia konfiguracji i uruchamiania wielu serwisów jednocześnie. Ustandaryzowana konfiguracja w pojedynczym pliku w formacie .yaml ułatwia wykrycie błędów i zarządzanie środowiskiem lokalnym dla kontenerów Dockera. Uruchamianie każdego kontenera z osobna za pomocą komendy docker run na dłuższą metę może okazać się problematyczne. Konfiguracja dla kontenerów znajduje się w pliku docker-compose.yaml.
services:
db:
image: mysql:9.4
container_name: db
restart: unless-stopped
environment:
- MYSQL_DATABASE=${MYSQL_DB}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
volumes:
- dbdata:/var/lib/mysql
networks:
- app-network
wordpress:
depends_on:
- db
image: wordpress:php8.4-fpm
container_name: wordpress
restart: unless-stopped
environment:
- WORDPRESS_DB_HOST=${MYSQL_HOST}
- WORDPRESS_DB_USER=${MYSQL_USER}
- WORDPRESS_DB_PASSWORD=${MYSQL_PASSWORD}
- WORDPRESS_DB_NAME=${MYSQL_DB}
volumes:
- wordpress:/var/www/html
networks:
- app-network
webserver:
depends_on:
- wordpress
image: nginx:1.29
container_name: webserver
restart: unless-stopped
ports:
- "80:80"
volumes:
- wordpress:/var/www/html
- ./nginx.conf:/etc/nginx/conf.d/nginx.conf
- certbot-etc:/etc/letsencrypt
networks:
- app-network
certbot:
depends_on:
- webserver
image: certbot/certbot
container_name: certbot
volumes:
- certbot-etc:/etc/letsencrypt
- wordpress:/var/www/html
command: certonly --webroot --webroot-path=/var/www/html --email adres_email --agree-tos --no-eff-email --staging -d nazwa_domeny -d www.nazwa_domeny
volumes:
certbot-etc:
wordpress:
dbdata:
networks:
app-network:
driver: bridgePrzed uruchomieniem kontenerów warto poznać w jaki sposób tworzyć kontenery w Docker Compose. Nasza konfiguracja składa się z czterech kontenerów utworzonych przy pomocy obiektu services, a jako atrybut została podana nazwa mikroserwisu (db, wordpress, webserver, certbot):
- image – Dla każdego kontenera należy wybrać obraz bazowy na którym zbudowany będzie dany kontener. Obrazy dla kontenerów można budować lokalnie z pliku .Dockerfile lub pobrać z repozytorium. W przypadku naszej konfiguracji nie posiadamy lokalnie żadnego Dockerfile więc obrazy zostaną pobrane z Docker Hub.
- container_name – W tym miejscu definiujemy nazwę dla kontenera.
- volumes – Służy do przechowywania danych kontenera lub współdzielenia plików hosta. Istnieją dwa rodzaje woluminów. Bind mounts polega zamontowaniu konkretnego folderu lub pliku hosta, dzięki czemu kontener może z nich korzystać. Druga opcja to Docker Volumes. Woluminy tworzone w ten sposób są zapisywane w folderze /var/lib/volumes/ a ich zawartość zostaje nawet po usunięciu kontenera. Woluminami tworzonymi w ten sposób można zarządzać z poziomu CLI Dockera. Operacje odczytu i zapisu są szybsze niż w przypadku Bind mounts. Tego typu woluminów używa się zazwyczaj kiedy nie potrzebujemy współdzielić żadnych plików hosta z kontenerem.
- ports – Służy do przekierowania żądań trafiających na określony port do portu kontenera, dzięki czemu możliwa jest interakcja z kontenerem z poziomu hosta.
- environment – Określenie zmiennych środowiskowych dla danego kontenera. Wartość zmiennej zadeklarowana w sposób ${nazwa_zmiennej} spowoduje pobranie wartości z pliku .env
- networks – Mówi nam z jakiej sieci stworzonej przez Dockera będą korzystać kontenery. Zdefiniowanie niestandardowej sieci typu bridge umożliwia komunikacje pomiędzy kontenerami po nazwie serwisu przy pomocy wewnętrznego DNS-a. Serwisy kontenerów będą dostępne na zdefiniowanych wcześniej portach hosta również dla innych urządzeń w tej samej sieci co host Dockera.
- restart – Definiuje zachowanie kontenera w przypadku zrestartowania usługi systemowej Dockera. Wartość unless-stopped zawsze uruchamia kontener ponownie z wyjątkiem gdy jest on zatrzymany manualnie.
- command – Uruchomienie danej komendy po starcie kontenera. Dla kontenera certbot, została zdefiniowana komenda odpowiedzialna za wygenerowanie certyfikatu.
- Opcja --webroot umieszcza pliki wtyczki webroot w celu autentykacji. Ta wtyczka opiera się na metodzie walidacji HTTP-01, która przy użyciu żądania HTTP udowadnia, że Certbot może uzyskać dostęp do zasobów z serwera, który reaguje na daną nazwę domeny.
- --webroot-path określa folder dla plików wtyczki. Wartość powinna pokrywać się z dyrektywą root z konfiguracji Nginxa dla lokacji ~ /.well-known/acme-challenge
- --email – Adres e-mail użyty do rejestracji i kontaktu w celu odzyskania danych
- --agree-tos – Zaakceptowanie regulaminu korzystania z serwerów Let’s Encrypta.
- --staging – Wygenerowanie certyfikatów testowych. Pozwala to na sprawdzenie poprawności działania konfiguracji przed przejściem w środowisko testowe. Certyfikaty wygenerowane z tą opcją korzystają również ze zwiększonego limitu żądań.
- -d – Określenie nazwy domeny dla żądań wygenerowania certyfikatu. Pamiętaj aby zastąpić wartość prawidłową nazwą swojej domeny.
Następnie uruchom kontenery poleceniem:
$ docker compose up -dOpcja -d (detached) uruchomi kontenery w tle bez zajęcia okna terminala logami z kontenerów. W celu sprawdzenia logów z wybranego kontenera możemy posłużyć się poleceniem:
$ docker compose logs <nazwa_serwisu> --followOpcja --follow służy do wyświetlania generowania logów serwisu w czasie rzeczywistym.
Możemy sprawdzić czy certyfikat znajduje się w folderze /etc/letsencrypt/live wewnątrz kontenera. Do wykonywania komend kontenera służy polecenie docker exec. Wykonując polecenie z opcją -it możemy uruchomić shell kontenera i utrzymać ten proces w obecnym oknie terminala.
$ docker exec -it webserver /bin/bashNastępnie znajdując się wewnątrz systemu plików kontenera
ls /etc/letsencrypt/liveOutput powinien wyświetlić zawartość folderu w którym znajduje się certyfikat dla twojej domeny.
total 16
drwx------ 3 root root 4096 Oct 1 10:04 .
drwxr-xr-x 7 root root 4096 Oct 1 10:15 ..
-rw-r--r-- 1 root root 740 Oct 1 10:04 README
drwxr-xr-x 2 root root 4096 Oct 1 10:15 nazwa_domenyWiedząc, że wygenerowanie certyfikatu w wersji testowej przebiegło pomyślnie, możemy uzyskać prawidłowy certyfikat SSL. W tym celu edytuj konfigurację serwisu certbot w pliku docker-compose.yaml. Należy zastąpić opcję --staging na --force-renewal. W ten sposób certbot ponownie wygeneruje certyfikat dla domeny.
certbot:
depends_on:
- webserver
image: certbot/certbot
container_name: certbot
volumes:
- certbot-etc:/etc/letsencrypt
- wordpress:/var/www/html
command: certonly --webroot --webroot-path=/var/www/html --email adres_email --agree-tos --no-eff-email --force-renewal -d nazwa_domeny -d www.nazwa_domenyPo zmianach w konfiguracji należy ponownie utworzyć kontener certbot
$ docker compose up --force-recreate --no-deps certbotOpcja --force-recreate ponownie tworzy kontener, niezależnie od tego, czy obrazy uległy zmianie, czy nie. Zapewnia ona, że wszelkie modyfikacje wprowadzone w pliku Dockerfile lub kontekście kompilacji zostaną zastosowane, a kontenery zostaną odbudowane od podstaw. Oprócz utworzenia kontenera startuje również serwisy które są od niego zależne (depends_on w definicji serwisu). Przekazując opcję --no-deps omijamy wystartowanie kontenera webserver, z uwagi na to że jest on już uruchomiony. Zwróć uwagę na logi z kontenera:
certbot | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot | Account registered.
certbot | Renewing an existing certificate for nazwa_domeny and www.nazwa_domeny
certbot |
certbot | Successfully received certificate.
certbot | Certificate is saved at: /etc/letsencrypt/live/nazwa_domeny/fullchain.pem
certbot | Key is saved at: /etc/letsencrypt/live/nazwa_domeny/privkey.pem
certbot | This certificate expires on 2025-12-30.
certbot | These files will be updated when the certificate renews.
certbot | NEXT STEPS:
certbot | - The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
certbot |
certbot | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
certbot | If you like Certbot, please consider supporting our work by:
certbot | * Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
certbot | * Donating to EFF: https://eff.org/donate-le
certbot | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
certbot exited with code 0Powyższy output mówi o tym że wygenerowanie certyfikatu SSL dla domeny przebiegło poprawnie. Wskazana jest lokalizacja gdzie zapisywany jest klucz oraz plik certyfikatu wraz z datą ważności certyfikatu, która wynosi 90 dni.
W tej sekcji zajmiemy się zmianą konfiguracji dla serwisu webserver, podmieniając plik nginx.conf dla kontenera i otwierając port 443 dla nasłuchiwania żądań HTTPS. W tym celu zatrzymaj kontener komendą
$ docker compose stop webserverNastępnie w pliku docker-compose.yaml wskaż plik nginx-ssl.conf w defincji woluminów dla kontenera webserver oraz dodaj otwarte porty w sekcji ports.
webserver:
depends_on:
- wordpress
image: nginx:1.29
container_name: webserver
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- wordpress:/var/www/html
- ./nginx-ssl.conf:/etc/nginx/conf.d/nginx.conf
- certbot-etc:/etc/letsencrypt
networks:
- app-networkPodobnie jak w przypadku zmiany konfiguracji dla certbota, należy ponownie utworzyć kontener wcześniej wspomnianą komendą:
$ docker-compose up -d --force-recreate --no-deps webserverPo uruchomieniu zweryfikuj czy uruchomione kontenery działają poprawnie. Kontenery bazy danych, wordpressa i serwera sieciowego powinny wskazywać na state UP.
$ docker psPo uruchomieniu kontenerów możesz dokończyć instalację WordPressa wpisując adres swojej domeny w przeglądarce. ![]()
Po upływie 90 dni certyfikat dla twojej domeny utraci ważność. W celu automatycznego odnawiania certyfikatu utworzymy prosty skrypt który będzie uruchamiał się okresowo przy pomocy narzędzia cron. Zwróc uwagę na plik w folderze repozytorium o nazwie cert-renew.sh.
#!/bin/bash
cd /home/nazwa_uzytkownika/folder_projektu/
docker compose run certbot renew --dry-run && docker compose exec webserver nginx -s reload && docker system prune -afSkrypt wykonuje trzy polecenia. Po wejściu w lokalizację projektu, uruchamia kontener certbot z komendą renew, która jest odpowiedzialna za odnowienie certyfikatu, restartuje konfiguracje nginxa wewnątrz kontenera webserver, a następnie usuwa nie używane obrazy i kontenery. Zwróć uwagę, że najpierw uruchamiamy odnowienie certyfikatu w trybie testowym z opcją --dry-run. Warto pamiętać, że certyfikaty, które nie utraciły daty ważności nie zostaną odnowione nawet po uruchomieniu skryptu.
Następnie skorzystamy z crona w celu zaplanowania okresowego uruchamiania skryptu. Dla przetestowania najpierw nadaj uprawnienia do wykonywania komendą:
$ chmod +x ./cert-renew.shPrzed napisaniem zadania w cron. Warto poznać składnie aby poprawnie zaplanować i uruchomić nasze zadania.

Na początku napiszmy zadanie które będzie sprawdzać ważność certyfikatu co 5 minut, w celu sprawdzenia czy wszystko przebiegło pomyślnie. Uruchom narzędzie cron poleceniem:
$ sudo crontab -eNastępnie uzupełnij o następującą zawartość:
*/5 * * * * /home/nazwa_uzytkownika/folder_projektu/cert-renew.sh >> /var/log/cron.log 2>&1Po 5 minutach sprawdź plik cron.log, który jest tworzony w celu zapisania danych wyjściowych, które powstały po wykonaniu skryptu. Poprawne odnowienie certyfikatu powinno wygenerować poniższy output.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/nazwa_domeny.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Simulating renewal of an existing certificate for nazwa_domeny and www.nazwa_domeny
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/nazwa_domeny/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2025/10/01 15:42:42 [notice] 33#33: signal process startedNastępnie usuń --dry-run ze skryptu cert-renew.sh
#!/bin/bash
cd /home/nazwa_uzytkownika/folder_projektu/
docker compose run certbot renew && docker compose exec webserver nginx -s reload && docker system prune -afPo poprawnym przetestowaniu skryptu zaplanujemy jego uruchomienie na godzinę 12:00 codziennie.
0 12 * * * /home/nazwa_uzytkownika/folder_projektu/cert-renew.sh >> /var/log/cron.log 2>&1Voila
w ten sposób udało się utworzyć wszystkie potrzebne serwisy w kontenerach do uruchomienia WordPressa za reverse proxy z domeną zabezpieczoną certyfikatem SSL wraz z automatycznym odnawianiem po upływie 90 dni. ![]()

