sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
Danach wird Docker installiert.
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Um die Installation und den Docker-Daemon zu testen, holt man sich den ersten Container und startet diesen.
docker run hello-world
Grundsätzlich kann man die Installation nun so belassen. Alle Benutzer können, wenn sie dies dürfen, per ''sudo'' Container bedienen.
Dies ist so, da der Daemon über einen Unix-Socket und nicht über einen TCP-Port erreichbar ist. Der Socket gehört dem Benutzer "root" und der Gruppe "docker".
Soll jeder Benutzer dies machen dürfen, auch ohne ''sudo'', so fügt man diesen der, bei der Installation hinzugefügten, Gruppe "docker" hinzu.
sudo usermod -aG docker
Hat man docker mit ''sudo'' bedient und hat sich danach der Gruppe "docker" hinzugefügt, so müssen noch die Rechte des eigenen Docker-Verzeichnisses angepasst werden.
mkdir -p /home//.docker
sudo chown -R : /home//.docker
sudo chmod -R g+rwx /home//.docker
===== Konfiguration =====
Die Konfiguration des Daemons wird mit Startparametern geregelt.
Diese werden normalerweise über die Datei ''/etc/docker/daemon.json'' gesetzt.
Bei Betriebssystemen mit "Systemd" als init-System werden die Parameter direkt in Systemd eigetragen.
Dazu erstellt man eine override.conf mit folgendem Befehl:
systemctl edit docker.service
Nun setzen wir z.B. den DNS-Server welcher an die Container übergeben werden soll.
Der Inhalt der override.conf sieht wie folgt aus:
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd --ip= --dns=1.1.1.1 --dns=8.8.8.8 --dns-search="dns.domain.tld" -H fd://
Die leere "ExecStart=" Zeile ist wichtig, da Systemd erst die Startsequenz löscht um sie dann neu zu definieren.
Nun noch Systemd die Änderungen mitteilen und dann den Docker-Daemon neustarten.
systemctl daemon-reload
systemctl restart docker
Note that the empty ExecStart= is required, as systemctl only will overrule the ExecStart if it is cleared first.
===== Autostart der Container nach Boot =====
Damit die Docker Container nach einem reboot des Docker Hosts automatisch starten, können folgende Methoden angewandt werden:
- starten über Prozessmanager wie ''systemd''
- starten über Docker Restart Policies
==== Systemd ====
Quelle: [[https://mehmandarov.com/start-docker-containers-automatically/]]
Per Befehl ''vi /etc/systemd/system/docker-[Unit]
Description=
Requires=docker.service
After=docker.service
[Service]
Restart=always
ExecStart=/usr/bin/docker start -a
ExecStop=/usr/bin/docker stop -t 2
[Install]
WantedBy=local.target
Erklärung:
''ExecStart=/usr/bin/docker start -a'' -> startet den Container im "attach" Modus
''ExecStop=/usr/bin/docker stop -t 2'' -> wartet 2 Sekunden auf den Stop des Container, bevor dieser gekillt wird
Nach dem Erstellen des Service diesen noch ''systemd'' bekannt machen und aktivieren:
systemctl daemon-reload
systemctl enable docker-.service
Gestartet werden kann der neue Service über die üblichen ''start'' und ''stop'' Kommandos.
==== Restart Policies ====
Quelle: [[https://docs.docker.com/config/containers/start-containers-automatically/]]
Mit dem ''run'' befehl dem Container sagen, welche restart Policy er erhalten soll:
docker run --restart [no|on-failure|always|unless-stopped]
^ Restart Policy ^ Beschreibung ^
| no | Do not automatically restart the container. (the default) |
| on-failure | Restart the container if it exits due to an error, which manifests as a non-zero exit code. |
| always | Always restart the container if it stops. If it is manually stopped, it is restarted only when Docker daemon restarts or the container itself is manually restarted. (See the second bullet listed in restart policy details) |
| unless-stopped | Similar to always, except that when the container is stopped (manually or otherwise), it is not restarted even after Docker daemon restarts. |
Mit foldendem Befehl kann man sehen welche Restart Policy aktuell im jew. Container gesetzt ist:
docker inspect graphite| jq '"Container: \(.[].Name)","Restart-Policy: \(.[].HostConfig.RestartPolicy.Name)"'
===== Debug =====
Mit der Startoption ''-D|--debug'' kann man das Debugging bei Docker einschalten.
Danach startet man die Log-Ansicht für einen Container wie folgt:
docker logs []
===== RUN, ENTRYPOINT, CMD =====
Die Befehle ''RUN'', ''ENTRYPOINT'' und ''CMD'' werden im ''Dockerfile'' unterschiedlich benutzt.
Grundsätzlich können diese Befehle als Exec und Shell Format übergeben werden.
**Exec**:
Beispiel [RUN|ENTRYPOINT|CMD] ["", "", ""]
* Im Exec Format wir der Befehl direkt so ausgeführt. Mit ''ENV'' gesetzte Variablen werden nicht übersetzt. Dazu muss der Befehl als Parameter für eine Shell angegeben werden (wie beim Shell Format)
* Wenn Bash ausgeführt werden muss, so sollte dies im Exec Format geschehen. Hier lassen sich Parameter eindeutig übergeben.
**Shell**:
Beispiel [RUN|ENTRYPOINT|CMD]
* Im Shell Format wird der Befehl mit Shell (''/bin/sh -c apt update && apt install -y \
nginx \
mysql
^ Befehl ^ Beschreibung ^ kann mehrmals \\ verwendet werden ^
| RUN | * wird verwendet um Befehle beim erstellen eines Images auszuführen | ja |
| Entrypoint | * wird verwendet um Befehle nach dem Start des Containers auszuführen \\ * wird auch ausgeführt wenn der ''docker run'' Befehl einen Standard Befehl am Ende ausführt \\ * wenn im Shell Format angegeben, wird jeder CMD Befehl ignoriert | ja |
| CMD | * wird verwendet um eine Standard Befehl nach dem start des Containers abzusetzen \\ * Wenn beim ''docker run'' Befehl ein Standard Befehl am Ende angegeben wird, wird ''CMD'' ignoriert \\ * Kann auch verwendet werden um nur Parameter an ''ENTRYPOINT'' zu übergeben, wenn kein Standard Befehl beim ''docker run'' übergeben wird, wird der ENTRYPOINT ausgeführt | nein (bzw. nur der letzte ''CMD''-Befehl wird ausgeführt) |
===== Konfigurationsparameter =====
Mögliche Parameter für Konfigurationen können in den Reference-Docs unter docker.com eingesehen werden [[https://docs.docker.com/reference/]].
===== Nützliche Befehle =====
dem Befehl ''docker'' können verschiedene Parameter übergeben werden.
^ Parameter ^ Beschreibung ^
| | |
| --version | zeigt die Version von Docker in Kurzfassung |
| version | zeigt die Version von Docker ausführlich |
| ps | zeigt laufende Container |
| build -t docker inspect -f '{{.Name}}-{{range .NetworkSettings.Networks}} {{.IPAddress}}{{end}}' $(docker ps -aq)
==== MACVLAN ====
Bei einem macvlan Netzwerk, werden die Container direkt im Netzwerk behandelt wie jeder normale Host. Sie sind quasi mit einer eigenen Netzwerkschnittstelle im Netzwerk präsent.
Container können mit dem Parameter ''ip_address'' (docker-compose) eine eigene feste IP-Adresse besitzen.
Soll ein DHCP-Server die IP-Adressen verwalten, so muss man bedenken, dass sich die MAC-Adresse eines Containers ändert, wenn dieser oder der Docker-Daemon neugestartet wird.
Dies kann mit dem Parameter ''mac_address'' (docker-compose: **IST IN VERSION 3 DEPRECATED**) angepasst werden und eine eindeutige MAC-Adresse vergeben werden.
MAC-Adressen werden vom Docker-Daemon generiert. Hier ein Auszug aus der [[https://docs.docker.com/engine/reference/run/#network-settings|Docker Reference]]: By default, the MAC address is generated using the IP address allocated to the container.
You can set the container’s MAC address explicitly by providing a MAC address via the --mac-address parameter (format:12:34:56:78:9a:bc).
Be aware that Docker does not check if manually specified MAC addresses are unique.
Eine einzigartige MAC-Adresse kann sich man aber relativ leicht zusammenstellen.
Wie immer geben die ersten 3 Stellen der MAC-Adresse den Hersteller (also die OUI => Organizationally Unique Identifier, „organisatorisch eindeutige Kennung“) an, bei Docker ist dies aber nicht der Fall.
Ein Docker-Daemon hat in diesem Fall die ersten 2 Stellen generiert, es geht also nur um die letzten 4 Stellen.
Dazu nimmt man einen Dezimal / Hexadezimal Rechner und wandelt die letzten 4 Oktette der IP-Adresse des Containers um.
Beispiel:
generierte MAC-Adresse: ''02:42:c0:a8:8a:0b''
Hier sind die Stellen ''02:42'' die festen Angaben, die letzten 4 würden in Dezimal folgendes ergeben ''192:168:138:11''.
Dies ist die IP-Adresse des Containers.
Hat man einen DCHP-Server, kann man bei diesem eine IP-Adresse für einen Container reservieren.
Danach rechnet man diese in Hexadezimal um und erhält somit die eindeutige MAC-Adresse.
=== Route von/zu Docker-Host ===
Generell sind Docker Container, welche sich in einem macvlan befinden, vom Docker Host isoliert (obwohl beide vielleicht im selben Subnetz sind.
Damit man die Verbindung wiederherstellen kann, muss eine Netzworkbridge mit einer entspr. Route zum Container/-Netzwerk erstellt werden.
Hier ein Beispiel (Quelle: [[https://www.networkshinobi.com/docker-host-cant-access-containers-running-on-macvlan/]]):
ip link add link type macvlan mode bridge
ip addr add dev
ip link set up
ip route add /32 dev
===== Images und Container =====
Images werden in Layer aufgeteilt. Einzelne Anweisungen im Dockerfile führen dazu, dass verschieden viele Layer erzeugt werden. Diese Layer sind alle schreibgeschützt.
Erst wenn der Container mit seinem Befehl gestartet wird, erzeugt er einen neuen eigenen letzen Layer, welcher beschrieben werden kann.
Dieser enthält die Daten welche zur Laufzeit hinzugekommen/geändert sind/wurden.
==== Erstellen ====
Um ein Image zu erstellen, benötigt man eine Datei namens ''Dockerfile''. Darin stehen die Anweisungen zum erstellen eines Containers.
Alle benötigten Dateien zum Erstellen eines Images, sollten sich in einem eigenen Ordner befinden.
Beispiel zum Erstellen eines Images mit Nginx und PHP:
mkdir nginx-php
cd nginx-php
=== Konfigurationsdateien ===
Folgende Dateien müssen erstellt und mit Inhalt gefüllt werden.
**Erklärung**
^ Datei ^ Beschreibung ^
| Dockerfile | * steuert das Erstellen des Images und das Startverhalten des Containers |
| package.list | * enthält die, in das Image, zu installierenden Pakete |
| startup.sh | * Startscript welches nach dem Starten des Containers ausgeführt wird \\ * hier werden die Daemons für nginx und php gestartet \\ * der letzte Befehl ist dafür da, damit das Script und somit auch der Container dauerhaft läuft |
| nginx.conf | * Konfiguration für nginx |
| my-website-conf | * Konfiguration für Webseite |
| fastcgi-php.conf | * Konfiguration für php und fastcgi |
| snakeoil.conf | * Konfiguration für die SSL Verschlüsselung mit Snakeoil Zertifikaten |
| ssl-cert-snakeoil.key | * SSL Zertifikat von Snakeoil |
| ssl-cert-snakeoil.pem | * SSL privater Schlüssel von Snakeoil |
| ssl-conf | * Konfiguration für SSL verschlüsselte Webseiten |
| dhparam.pem | * Diffie Hellmann Schlüssel |
| index.php | * Startseite |
| phpinfo.php | * PHP-Info Webseite |
| hallo.html | * SSL verschlüsselte Webseite |
* ''Dockerfile'' FROM ubuntu:bionic
# Define environment variable
ENV WWW_DIR /var/www/html
ENV TERM xterm
ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
ENV DEBIAN_FRONTEND noninteractive
# set Timezone with `--build-arg TIMEZONE=Europe/Berlin` in `docker build`
ARG TIMEZONE
# Set the working directory to /var/www/html
WORKDIR /var/www/html
# Copy files
ADD *.html /var/www/html/
ADD *.php /var/www/html/
ADD package.list /tmp
ADD timezone /etc
# Install any needed packages specified in requirements.txt
RUN \
apt update && \
apt upgrade -y && \
apt install -y apt-utils && \
cat /tmp/package.list | xargs apt install -y
# set Timezone
RUN if [ ! -z "${TIMEZONE}" ]; \
then ln -sf /usr/share/zoneinfo/$TIMEZONE /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata; \
fi
# Create directories
RUN mkdir -p /etc/ssl/certs /etc/ssl/private /var/log/nginx
# Install Snakeoil Cert and Key
ADD ssl-cert-snakeoil.pem /etc/ssl/certs
ADD ssl-cert-snakeoil.key /etc/ssl/private
# Copy Nginx Configs
ADD nginx.conf /etc/nginx/
ADD fastcgi-php.conf /etc/nginx/snippets/
ADD snakeoil.conf /etc/nginx/snippets/
ADD dhparam.pem /etc/nginx/snippets/
ADD ssl-conf /etc/nginx/sites-available/
ADD my-website-conf /etc/nginx/sites-available/
ADD startup.sh /usr/local/bin
RUN \
ln -s /etc/nginx/sites-available/my-website-conf /etc/nginx/sites-enabled && \
unlink /etc/nginx/sites-enabled/default
# Define mountable directories.
VOLUME ["/etc/nginx", "/var/log", "/var/www/html"]
# Make ports 80 and 443 available
EXPOSE 80
EXPOSE 443
ENTRYPOINT ["/bin/bash", "-c", "startup.sh"]
* ''package.list'' vim rsyslog nginx php-fpm fcgiwrap openssl
* ''startup.sh'' #!/bin/bash
while ! pgrep nginx; do
service nginx start
sleep 1
done
while ! pgrep php-fpm; do
service php7.2-fpm start
sleep 1
done
tail -f /dev/null
* ''nginx.conf'' user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
## FCGI: Detect when HTTPS is used
map $scheme $fastcgi_https {
default off;
https on;
}
upstream php {
server unix:/var/run/php/php7.2-fpm.sock;
}
upstream fcgiwrap {
server unix:/var/run/fcgiwrap.socket;
}
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
* ''my-website-conf'' server {
listen 80;
server_name _;
root /var/www/html;
index index.html index.htm index.php;
location / {
index index.html index.htm index.php;
try_files $uri $uri/ /index.php;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
try_files $fastcgi_script_name =404;
}
# We don't want to allow the browsers to see .hidden linux/unix files
location ~ /\. { deny all; access_log off; log_not_found off; }
}
server {
listen 443 ssl;
include snippets/snakeoil.conf;
include /etc/nginx/sites-available/ssl-conf;
root /var/www/html;
index index.html index.htm index.php;
server_name _;
location / {
index index.php;
try_files $uri $uri/ /index.php;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
try_files $fastcgi_script_name =404;
}
# We don't want to allow the browsers to see .hidden linux/unix files
location ~ /\. { deny all; access_log off; log_not_found off; }
}
* ''fastcgi-php.conf'' # regex to split $uri to $fastcgi_script_name and $fastcgi_path
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# Check that the PHP script exists before passing it
#try_files $fastcgi_script_name =404;
# Bypass the fact that try_files resets $fastcgi_path_info
# see: http://trac.nginx.org/nginx/ticket/321
set $path_info $fastcgi_path_info;
fastcgi_param PATH_INFO $path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
fastcgi_index index.php;
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
fastcgi_read_timeout 240;
* ''snakeoil.conf'' # Self signed certificates generated by the ssl-cert package
# Don't use them in a production server!
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
* ''ssl-cert-snakeoil.pem'': SSL-Zertifikat kann vom lokalen Docker Host kopiert werden
* ''ssl-cert-snakeoil.key'': SSL privater Schlüssel kann vom lokalen Docker Host kopiert werden
* ''ssl-conf'' #SSL-Protocols
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
#HSTS Security
add_header Strict-Transport-Security max-age=15768000;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
# openssl dhparam -out dhparam.pem 2048
ssl_dhparam /etc/nginx/snippets/dhparam.pem;
##ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK';
ssl_prefer_server_ciphers on;
* ''dhparam.pem'' Diese Datei muss über ''openssl'' einmalig, auf dem Docker-Host, erzeugt werden openssl dhparam -out dhparam.pem 2048
* ''index.php'': Hier noch die IP Adresse oder Hostnamen anpassen
NGINX im Docker-Container
Testseiten
zeige PHP-Info
zeige PHP-Info
* ''phpinfo.php''
* ''hallo.html''
NGINX im Docker-Container
Webseite SSL verschlüsselt
Diese Seite ist SSL verschlüsselt.
=== Build and Run ===
Nun kann man das Docker-Image bauen lassen. Dazu führt man folgenden Befehl aus:
docker build --build-arg TIMEZONE=Europe/Berlin -t .
Mit folgendem Befehl sieht man das nun erstellte Image
docker image ls
Das Images ist nun erstellt. Nun kann man seinen Docker-Container starten.
docker run -d -p 80:80 -p 443:443 --name nginx :
Zur Erklärung der Parameter:
* ''-d'' steht für "detach". Der Container wird dann im Hintergrund ausgeführt.
* ''-p'' stellt Ports welche innerhalb des Containers laufen nach außen bereit. docker container ls
Dieser Befehl beendet den jew. Container
docker container stop
===== Volumes =====
Damit man wegen Konfigurationsänderungen nicht ständig zum Container verbinden muss, erstellt man sich Docker-Volumes und mountet diese mit den Volumes welche im Dockerfile angegeben wurden.
Die Volumes werden folgendermaßen angelegt:
docker volume create
Der Docker Container wird dann so gestartet:
docker run -d -p 80:80 -p 443:443 -v nginx-nginx:/etc/nginx -v nginx-log:/var/log -v nginx-html:/var/www/html :
Zur Erklärung der Parameter:
* ''-v'' mountet Verzeichnisse aus dem Container in Volumes auf dem Docker-Host oder anderer Container. docker save -o .tar
* **importieren** docker load -i .tar
* Export/Import
* **sichern** docker export -o .tar
* **importieren** docker import .tar
===== Portainer =====
Portainer ist ein Docker Management Tool, welches auch als Container läuft.
Siehe [[https://www.portainer.io/]].
==== Installation ====
docker volume create portainer_data
$ docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest
==== Upgrade ====
docker stop portainer
docker pull portainer/portainer
docker container rm portainer
Danach einfach Portainer wie bei der Installation beschrieben, starten.