Als Webentwickler braucht man oftmals eine entsprechende Entwicklungsumgebung um Projekte schnell und zuverlässig realisieren zu können. Ein Coding Tool ist meistens schnell gefunden und da hat ja auch jeder so ein bisschen seine Vorliebe.
Interessanter wird es dann schon bei einer Serverumgebung mit der man die Projekte realisieren und testen kann. Viele greifen hierbei auf einen fertigen LAMP Stack zurück wie zb. xampp oder mamp zurück. Diese ist aber eher selten die Version die der Kunde am Ende auch für den produktiven Betrieb einsetzt. Das kann zur Folge haben, dass die Entwickelte Software beim Kunden nicht läuft.
Dem ganzen kann man entgegenwirken, indem man auf seinem Entwicklungssystem einen entsprechendes Serversetup mit Docker realisiert. Damit spart man sich das erwerben teuerer Hardware oder das mieten von Systemen.
Ein weitere Vorteil ist hierbei, dass man dem Kunden zum Betrieb der Applikation einfach die Dockercontainer übergeben kann und damit sicher ist das diese auch wie geplant funktioniert.
Wie man so eine Entwicklungsumgebung mit Docker aufbaut werde ich nun im Verlauf dieses Artikels zeigen.
Inhaltsverzeichnis
Anforderungsanalyse
Zuerst sollten wir uns Gedanken machen, was wir für unser eigenes Entwicklungssetup in Docker brauchen. Das kann je nach Entwickler und Projekten sehr unterschiedlich sein. Die einen brauchen ein Node.js Setup, der nächste macht alles mit Python Flask.
Ich habe mich hier entschieden ein Setup für die Webentwicklung mit php und MySQL zu realisieren. Dafür brauch ich die folgenden Komponenten:
- nginx
- php
- MySQL
- phpMyAdmin
Um das ganze zu realisieren, muss auf dem System als erste Docker installiert werden. Wie das genau funktioniert könnt ihr zb. in meinem Artikel über die Installation von Docker unter CentOS sehen
Anlegen der Docker Struktur
Um das Setup zu realisieren, brauchen wir ein paar Dockerfiles und Ordner. Beim mir sieht das ganze wie folgt aus:
1 2 3 4 5 6 7 8 9 | docker-web/ ├── docker-compose.yml ├── nginx │ ├── Dockerfile │ └── default.conf ├── php │ └── Dockerfile └── sites └── index.php |
Erstellen der Dateien
Als nächstes müssen wir die oben gesehen 5 Dateien anlegen. Dies werden wir nun Schritt für Schritt machen.
nginx/Dockerfile
Wir starten mit dem Dockerfile für nginx. Dies sieht bei mir wie folgt aus:
1 2 3 | FROM nginx:latest COPY ./default.conf /etc/nginx/conf.d/default.conf |
In diesem Dockerfile passiert nicht sehr viel. Wir legen die letzte Version des nginx Containers als Quelle an und kopieren die Datei default.conf in den Dockercontainer.
nginx/default.conf
In dieser Datei wird die Konfiguration des nginx Dienstes festgelegt. Das ganze sieht wie folgt aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | server { listen 80 default_server; root /var/www/html; index index.html index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } access_log off; error_log /var/log/nginx/error.log error; sendfile off; client_max_body_size 100m; location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass php:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_intercept_errors off; fastcgi_buffer_size 16k; fastcgi_buffers 4 16k; } location ~ /\.ht { deny all; } } |
Meine Konfiguration unterscheidet sich hier nicht wirklich von einer Standard nginx Konfiguration.
php/Dockerfile
Weiter geht es mit dem Dockerfile für php. Dieses hat bei mir den folgenden Inhalt:
1 2 3 | FROM php:7.0-fpm RUN docker-php-ext-install pdo_mysql |
Hier legen wir die php 7 fpm Container als Quellimage fest. In diesem Container installieren wir anschließend noch die pdo_mysql Extension damit wir mit der MySQL Datenbank kommunizieren können.
sites/index.php
Hierbei handelt es sich um eine HelloWorld Datei mit der wir am Ende die Funktion des Setups testen wollen. In dem Sites Ordner könne später unsere Projektordner abgelegt oder gelinkt werden.
Der Testinhalt der index.php sieht bei mir wir folgt aus:
1 2 3 4 5 6 7 8 9 10 | <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Hello World!</title> </head> <body> <h1>Hello World</h1> </body> </html> |
docker-compose.yml
Zu guter letzt kommt das docker-compose.yml. Der Inhalt sieht wie folgt aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | nginx: build: ./nginx/ ports: - 80:80 links: - php volumes_from: - app php: build: ./php/ expose: - 9000 links: - mysql volumes_from: - app app: image: php:7.0-fpm volumes: - ./sites:/var/www/html command: "true" mysql: image: mysql:latest volumes_from: - data environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: project MYSQL_USER: project MYSQL_PASSWORD: project data: image: mysql:latest volumes: - /var/lib/mysql command: "true" phpmyadmin: image: phpmyadmin/phpmyadmin ports: - 8080:80 links: - mysql environment: PMA_HOST: mysql |
Wie so eine Compose Datei funktioniert habe ich bereits in dem Artikel Docker Compose – Automatisiertes starten von Microservices genauer erklärt. Aber schauen wir uns die einzlenen Blöcke etwas geanuer an.
Im ersten Block rufen wir das Dockerfile für nginx auf. Des weiteren wird der Port gestgelegt, unter dem der Webserver laufen soll. Danach gibt es noch einen Link zum php Container da mit diesem interagiert werden soll und es wird festgelegt, dass das Volumen ,welches im app Block definiert wird, verwendet werden soll.
Der zweite Block ist vom Aufbau her ähnlich wie der nginx Block.
Im dritten Block definieren wir ein paar spezifische Punkte wie zb. der Ordner in dem unsere Projektdateien liegen. Dieser wird in den Container nginx Container eingetragen.
Im vierten Block wird ein Datenbankcontainer definiert. Dieser bekommt als Speicherort das hinterlegt, was im nächsten Block (data) hinterlegt wird. Im Abschnitt environment definieren wir noch die Datenbank sowie User und Passwort.
Im fünften Block wird der Speicherort der Datenbank definiert. Dieser wird nach Möglichkeit immer außerhalb eines Containers gelegt, da es sonst zu Problemen kommen kann wenn der Container einmal innerhalb einer Transaktion unterbrochen wird.
Im sechsten und letzten Block fügen wir unserem Setup noch phpMyAdmin hinzugefügt. Dadurch können wir Datenbankeinstellungen über ein Webfrontend tätigen.
Starten des Setups
Damit wir die Container nun verwenden können, müssen wir das Setup starten. Dafür verwendet man den Befehl docker-compose im Verzeichnis in dem die Datei docker-compose.yml liegt. Das ganze kann dann wie folgt aussehen.
1 2 3 4 5 6 7 | docker-compose up -d Creating docker-web_app_1 ... done Creating docker-web_data_1 ... done Creating docker-web_mysql_1 ... done Creating docker-web_phpmyadmin_1 ... done Creating docker-web_php_1 ... done Creating docker-web_nginx_1 ... done |
Startet man das Setup zum Ersten mal, ist die Ausgabe deutlich länger da die Container noch heruntergeladen werden.
Anschließend können wir mit localhost und dem definierten Port aus der docker-compose.yml die php Seite aufrufen:
Das selbe funktioniert nun auch mit localhost und dem für phpMyAdmin definiert Port.
Michael Roth meint
Hübscher Artikel. Allerdings verstehe ich nur relativ Bahnhof. Schon die von dir angelegte Docker Struktur kann ich nicht mehr nachvollziehen. Zudem will mir nicht einleuchten, dass beim Erstellen einer Entwicklungsumgebung der nginx-Container den Port 80 verwendet. Dieser ist im Regelfall sowieso schon in Verwendung und kollidiert dann zwangsläufig. Und zu guter letzt: Warum nginx und nicht apache?
Arne Sauer meint
Hallo Michael,
Einfach statt 80:80 8081:80 verwenden dann läuft dein nginx auf port 8081.
nginx wird inzwischen sehr häufig verwendet, weil leichter zu administrieren.
Arne Sauer meint
Sehr schöner Artikel. Mich interessiert vor allem das Thema Deployment. Das heisst was ist Best Practice um meine Entwicklung vom lokalen Rechner auf die Produktion zu bringen.
Build nochmal auf Produktion? Image kopieren? Sind die lokalen Änderungen automatisch im Image? Lokale Entwicklung separat über Git auf Produktion schieben und dort neuen Build machen?
Falls da jemand etwas Licht reinbringen könnte, wäre super.
Gruss Arne
Christian Piazzi meint
Hi Arne,
ich bin mir ehrlich gesagt nicht sicher ob es da einen Best Practice gibt.
Aber bei verschiedenen Umgebungen würde ich einfach eine Docker Registry installieren.
https://www.modius-techblog.de/devops/docker-registry-aufbau-eines-privaten-docker-repositories/
Hier kannst du dann entweder die Images mit Versionsnummer versehen und die stabile Version in Production laden.
Habe auch schon gesehen, dass einige nur die 3 Tags deprecated, stable und experimentel verwenden. Das Aktuelle Production Image bekommt dann immer den stable Tag, der Vorgänger bekommt deprecated um das Image noch zu haben, falls es Probleme gibt. In dev läuft dann die experimentel Version.
Ich hoffe das hilft dir ein bisschen weiter.
Gruß
Christian
Arne Sauer meint
Hallo Christian,
ganz vielen Dank für die schnelle Antwort. Die Antwort hilft schon gut weiter, da ich ansonsten noch Stunden mit der Suche nach der „Standardlösung“ verbracht hätte. So probier ich einfach mal verschiedene Wege aus und taste mich langsam an das Thema CI/CD und Pipelines heran.
VG Arne
Christian Piazzi meint
Hi,
cool. Du kannst mich ja mal auf dem laufenden halten, was du da baust. Vielleich können wir dann dazu ja dann einen Artikel hier machen =)