mein Docker-Webserver Setup f├╝r Laravel - Konfig im Detail


Wie bereits angek├╝ndigt, habe ich f├╝r meine Webseiten Plesk mit Docker ersetzt. Zwar verwalte ich die Seiten jetzt ├╝ber das Terminal und nicht mehr ├╝ber eine GUI, daf├╝r bin ich mit dem Setup aber wesentlich flexibler: Ich kann die Container jederzeit kopieren und auf einem anderen Server starten, oder schneller mal eine neue Seite online stellen, bzw. f├╝r eine bestimmte Seite den Webserver oder die PHP-Version tauschen. Auch der Einsatz neuer Features wie Laravel Octane und Swoole sind damit einfach realisierbar. Der Beweggrund f├╝r dieses Setup kann auf folgender Seite nachgelesen werden: Docker vs. Plesk, f├╝r den Betrieb von Webseiten.

Docker Basics

Docker erm├Âglicht es, Services oder Applikationen per Befehl in einem sogenannten Container zu starten.
Ein Container ist eine vom Betriebssystem (OS) unabh├Ąngige isolierte Umgebung:
Beim ersten Start eines Containers, l├Ądt Docker selbstst├Ąndig alle notwendigen Quellen
aus dem Internet.
Docker kann unter Windows, macOS oder einer Linux-Distribution installiert werden,
siehe auch: Docker
In diesem Beitrag habe ich meine aktuellen Webserver-Konfig-Dateien zusammengefasst. Für mein Setup habe ich alle notwendigen Services für den Betrieb eines Laravel-Webservers in einen Container gepackt, mit Ausnahme der Datenbank. Für das Starten der Prozesse verwende ich Supervisor.

Dem Webserver Nginx verwende ich in beiden F├Ąllen um statischen Content direkt auszuliefern und nicht ├╝ber den PHP-Webserver. Als Webserver f├╝r die PHP-Applikation habe ich zwei verschiedene Varianten getestet: Einmal mit php-fpm, einmal mit swoole als Web-Worker

Variante php-fpm:

  • supervisord,
  • Nginx,
  • php-fpm,
  • redis und
  • cron

Variante swoole:

  • supervisord,
  • Nginx,
  • swoole,
  • redis und
  • cron

Cron k├Ânnte f├╝r Laravel auch ├╝ber den Host gestartet werden, also ein Eintrag der jede Minute im Container des Webservers den Laravel Scheduler startet.
Mir gef├Ąllt der Ansatz Cron auch im Container zu starten etwas besser, da der Container f├╝r dieses Setup nicht extra in der Crontab des Hosts hinterlegt werden muss und ohne extra Konfiguration Out-of-the-box funktioniert.

Webserver-Konfig Laravel-Webseite: Variante mit php-fpm

F├╝r Docker habe ich folgende Konfig-Dateien angelegt:

docker-compose.yml

[+]
version: '3'
services:
  web:
    container_name: laravel_web
    restart: always
    build:
      context: .
      dockerfile: Dockerfile
    expose:
      - "80"   
  #F├╝r einen direkten Test-Zugriff, in den folgenden 2 Zeilen "#" entfernen. Aufruf: http://localhost:83 oder http://ServerIP:83
   #ports:   
    #- "83:80" 
   #Labels f├╝r ReverseProxy, siehe: https://www.libe.net/traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.home.rule=Host(`laravel.domain.tld`)"      
      - "traefik.http.routers.home.entrypoints=web"
      - "traefik.http.routers.home.entrypoints=websecure"
      - "traefik.http.routers.home.tls.certresolver=myresolver"
      - "traefik.http.services.home.loadbalancer.server.port=80"
    volumes:
      - "./www:/var/www"
  mysql:
    image: 'mysql'
    container_name: laravel_mysql
    environment:
        MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
        MYSQL_DATABASE: '${DB_DATABASE}'
        MYSQL_USER: '${DB_USERNAME}'
        MYSQL_PASSWORD: '${DB_PASSWORD}'   
    restart: always
    volumes:
        - './db:/var/lib/mysql'
    healthcheck:
      test: ["CMD", "mysqladmin", "ping"]

#Ohne dem Einsatz eines Reverse Proxy (https://www.libe.net/traefik) fehlt voraussichtlich das Netzwerk webproxy 
#und die folgenden Zeilen k├Ânnen entfernt oder auskommentiert werden. Alternativ kann das Netzwerk mit "docker network create webproxy" erstellt werden
networks:
  default:
    external:
      name: webproxy

Als Unterordner f├╝r die Datenbank verwende ich: "db" und f├╝r die Webseite: "www"

Um f├╝r die Webseite ein SSL-Zertifikat zu verwenden, habe ich den Traefik-Reverse-Proxy vorgeschaltet, daher beinhaltet die docker-compose.yml das Netzwerk "webproxy". Die Variablen f├╝r die Datenbank DB_DATABASE etc. habe ich in der .env-Datei von Laravel hinterlegt, diese k├Ânnen beim Starten des Containers ├╝ber den Parameter "--env" ├╝bergeben werden, der Build-Prozess kann mit "--build" angesto├čen werden und damit der Container im Hintergrund gestartet wird, als Parameter ÔÇ×-dÔÇť verwendet werden.

 docker-compose --env-file ./www/.env up -d --build 

Dockerfile

Folgendes Setup beinhaltet PHP-FPM und Swoole als PHP-Extension. Je nach Webserver-Setup kann FPM oder Swoole verwendet, bzw. die nicht verwendete Variante entfernt werden:

[+]
FROM ubuntu:20.04

ARG WWWGROUP

WORKDIR /var/www

ENV DEBIAN_FRONTEND noninteractive
ENV TZ=UTC
ENV PHP_VERSION 8.0

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# Install Dependencies like in Laravel Sail:
RUN apt-get update \
    && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev tesseract-ocr python2 \
    && mkdir -p ~/.gnupg \
    && chmod 600 ~/.gnupg \
    && echo "disable-ipv6" >> ~/.gnupg/dirmngr.conf \
    && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys E5267A6C \
    && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C300EE8C \
    && echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu focal main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
    && apt-get update \
    && apt-get install -y php${PHP_VERSION}-cli php${PHP_VERSION}-dev \
       php${PHP_VERSION}-pgsql php${PHP_VERSION}-sqlite3 php${PHP_VERSION}-gd \
       php${PHP_VERSION}-curl php${PHP_VERSION}-memcached \
       php${PHP_VERSION}-imap php${PHP_VERSION}-mysql php${PHP_VERSION}-mbstring \
       php${PHP_VERSION}-xml php${PHP_VERSION}-zip php${PHP_VERSION}-bcmath php${PHP_VERSION}-soap \
       php${PHP_VERSION}-intl php${PHP_VERSION}-readline \
       php${PHP_VERSION}-msgpack php${PHP_VERSION}-igbinary php${PHP_VERSION}-ldap \
       php${PHP_VERSION}-gmp php${PHP_VERSION}-mbstring php${PHP_VERSION}-redis \
    && php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
    && curl -sL https://deb.nodesource.com/setup_15.x | bash - \
    && apt-get install -y nodejs \
    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
    && apt-get update \
    && apt-get install -y yarn \
    && apt-get install -y mysql-client \
    && apt-get -y autoremove \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# add nginx
RUN apt-get update && apt-get install -y software-properties-common && apt-add-repository ppa:nginx/stable -y && apt-get install -y php${PHP_VERSION}-fpm nginx && \
    mkdir -p /run/php && chmod -R 755 /run/php && \
    sed -i 's|.*listen =.*|listen=9000|g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \
    sed -i 's|.*error_log =.*|error_log=/proc/self/fd/2|g' /etc/php/${PHP_VERSION}/fpm/php-fpm.conf && \
    sed -i 's|.*access.log =.*|access.log=/proc/self/fd/2|g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \
    sed -i 's|.*user =.*|user=root|g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \
    sed -i 's|.*group =.*|group=root|g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \
    sed -i -e "s/;catch_workers_output\s*=\s*yes/catch_workers_output = yes/g" /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \
    sed -i 's#.*variables_order.*#variables_order=EGPCS#g' /etc/php/${PHP_VERSION}/fpm/php.ini && \
    sed -i 's#.*date.timezone.*#date.timezone=UTC#g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \
    sed -i 's#.*clear_env.*#clear_env=no#g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \
    sed -i 's#.*pm = dynamic*#pm = ondemand#g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \
    sed -i '/pm.max_children = /c\pm.max_children = 50' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \
    sed -i '/pm.process_idle_timeout = /c\pm.process_idle_timeout = 60s' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \
    sed -i '/pm.max_requests = /c\pm.max_requests = 15' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf 

# add swoole
RUN pecl install --configureoptions 'enable-sockets="no" enable-openssl="no" enable-http2="no" enable-mysqlnd="no" enable-swoole-json="no" enable-swoole-curl="no"' swoole
#You should add "extension=swoole.so" to php.ini
RUN echo "extension=swoole.so" > /etc/php/${PHP_VERSION}/cli/conf.d/99-php.ini
RUN echo "extension=swoole.so" > /etc/php/${PHP_VERSION}/fpm/conf.d/99-php.ini

# add redis
RUN apt-get update && apt-get install -y redis-server

# add cron
RUN apt-get install -y cron
RUN echo "* * * * * root /usr/bin/php /var/www/artisan schedule:run >> /dev/null 2>&1" > /etc/cron.d/laravel-scheduler
RUN chmod 644 /etc/cron.d/laravel-scheduler

# Add user for laravel application
RUN groupadd -g 1000 www
RUN useradd -u 1000 -ms /bin/bash -g www www

#for supervisor to start the right version:
RUN mv /usr/sbin/php-fpm${PHP_VERSION} /usr/sbin/php-fpm 

COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY php.ini /etc/php/${PHP_VERSION}/cli/conf.d/99-php.ini
COPY php.ini /etc/php/${PHP_VERSION}/fpm/conf.d/99-php.ini
COPY nginx.conf /etc/nginx/nginx.conf
COPY mysql.cnf /etc/mysql/conf.d/mysql.cnf

CMD /usr/bin/supervisord
EXPOSE 80

supervisord.conf

Um die notwendigen Prozesse zu ├╝berwachen und zu starten, verwende ich Supervisor, hier meine supervisord.conf f├╝r die php-fpm-Variante:

[+]
[supervisord]
nodaemon=true
user=root
logfile=/var/log/supervisord.log
pidfile=/var/run/supervisord.pid

[program:nginx]
command=/usr/sbin/nginx
autostart = true
autorestart=true
stdout_logfile=/dev/nginx-stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/nginx-stderr
stderr_logfile_maxbytes=0

[program:php-fpm]
command=/usr/sbin/php-fpm -R --nodaemonize
autostart=true
autorestart=true
stdout_logfile=/var/log/php-fpm-stdout.log
stdout_logfile_maxbytes=0
stderr_logfile=/var/log/php-fpm-stderr.log
stderr_logfile_maxbytes=0
exitcodes=0

[program:redis]
command=redis-server
autostart=true
autorestart=true
stdout_logfile=/var/log/redis-stdout.log
stdout_logfile_maxbytes=0
stderr_logfile=/var/log/redis-stderr.log
stderr_logfile_maxbytes=0
exitcodes=0

[program:cron]
command=cron
autostart=true
autorestart=true
stdout_logfile=/var/log/cron-stdout.log
stdout_logfile_maxbytes=0
stderr_logfile=/var/log/cron-stderr.log
stderr_logfile_maxbytes=0
exitcodes=0

nginx.conf

Die Nginx-Konfiguration unterscheidet zwischen statischem Content, PHP-Seiten und abh├Ąngig von einem bestimmten Cookie, ob statische gecachte Files verwendet werden sollen, oder die Anfrage zu php-fpm geschickt werden soll. F├╝r das Caching der statischen Files verwende ich das Laravel-Paket page-cache (siehe auch: JosephSilber/page-cache und┬áWebseite Stresstest - Performance messen Anfragen/Sekunde).┬á

[+]
#worker_processes  2;
daemon off;
user root;

#pid        logs/nginx.pid;
events {
    worker_connections  1024;
}

error_log /dev/stdout info;

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    sendfile on;
    keepalive_timeout  65;
    gzip  on;
    gzip_vary on;
    gzip_min_length 10240;
    gzip_proxied any;
    gzip_disable msie6;
    gzip_comp_level 1;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    error_log /dev/stdout;

    server {
        listen 80 ;

        server_name _;

#        auth_basic           "Test Area";
#        auth_basic_user_file /var/www/.htpasswd;
        root /var/www/public;


        #redirect index.php
        if ($request_uri ~* "^/index\.php/(.*)") {
            return 301 /$1;
        }


        location ~* ^/storage/.*\.(js|css|png|jpg|jpeg|gif|svg|ico)$ {
            expires 7d;
            add_header Cache-Control "public, no-transform";
        }


        #set variables for Cache...
        set $shouldusecache4root @usecache4root;
        set $shouldusecache4pages @usecache4pages;
        if ($http_cookie ~* "nocache=YES(?:;|$)") {
            set $shouldusecache4root @nocache4root;
            set $shouldusecache4pages @nocache4pages;
        }
        if ($query_string != "") {
            set $shouldusecache4root @nocache4root;
            set $shouldusecache4pages @nocache4pages;
        }
        #hack locations...
        location = / {
            try_files /dev/null $shouldusecache4root;
        }
        location / {
            try_files /dev/null $shouldusecache4pages;
        }
        #use named locations from hacked locations...
        location @usecache4root {
            try_files /page-cache/pc__index__pc.html /index.php?$is_args$args;
        }
        location @nocache4root {
            try_files $uri /index.php?$is_args$args;
        }

        location @usecache4pages {
            try_files $uri $uri/ /page-cache/$uri.html /page-cache/$uri.json /index.php$is_args$args;
        }
        location @nocache4pages {
            try_files $uri $uri/ /index.php$is_args$args;
        }
        

        location ~ ^/index\.php(/|$) {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_split_path_info ^(.+\.php)(/.*)$;
            include /etc/nginx/fastcgi_params;

            fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
            fastcgi_param DOCUMENT_ROOT $realpath_root;

            # Prevents URIs that include the front controller. This will 404:
            # http://domain.tld/app.php/some-path
            # Remove the internal directive to allow URIs like this
            internal;
        }

    }
}

php.ini

[PHP]
post_max_size=512M
upload_max_filesize=512M
variables_order=EGPCS
max_execution_time=60

[opcache]
opcache.enable=1
; 0 means it will check on every request
; 0 is irrelevant if opcache.validate_timestamps=0 which is desirable in production
opcache.revalidate_freq=0
opcache.validate_timestamps=1
opcache.max_accelerated_files=30000
opcache.memory_consumption=256
opcache.max_wasted_percentage=10
opcache.interned_strings_buffer=16
opcache.fast_shutdown=1

mysql.cnf - Tuning mysql Memory Usage

Um mehrere Webseiten zu betreiben, kann der Speicherplatz von mysql optimiert werden, indem das Performance-Schema deaktiviert wird: Anstelle von ├╝ber 500 MB, ben├Âtigt mysql bei meiner Webseite ohne dem Performance-Schema nur mehr ca. 300 MB / Container

[mysqld]
performance_schema = 0
expire_logs_days = 2
key_buffer_size = 5M
innodb_buffer_pool_size = 60M

Laravel Swoole und Octane: Variante mit swoole

F├╝r den Einsatz von Swoole als Webserver f├╝r Laravel Octane muss lediglich die Dateien php.ini, nginx.conf und supervisord.conf angepasst werden:

supervisord.conf

[+]
[supervisord]
nodaemon=true
user=root
logfile=/var/log/supervisord.log
pidfile=/var/run/supervisord.pid

[program:nginx]
command=/usr/sbin/nginx
autostart = true
autorestart=true
stdout_logfile=/dev/nginx-stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/nginx-stderr
stderr_logfile_maxbytes=0

[program:octane]
command=/usr/bin/php -d variables_order=EGPCS /var/www/artisan octane:start --server=swoole --watch --host=0.0.0.0 --port=8000
autostart=true
autorestart=true
stdout_logfile=/var/log/php-fpm-stdout.log
stdout_logfile_maxbytes=0
stderr_logfile=/var/log/php-fpm-stderr.log
stderr_logfile_maxbytes=0
exitcodes=0

[program:redis]
command=redis-server
autostart=true
autorestart=true
stdout_logfile=/var/log/redis-stdout.log
stdout_logfile_maxbytes=0
stderr_logfile=/var/log/redis-stderr.log
stderr_logfile_maxbytes=0
exitcodes=0

[program:cron]
command=cron
autostart=true
autorestart=true
stdout_logfile=/var/log/cron-stdout.log
stdout_logfile_maxbytes=0
stderr_logfile=/var/log/cron-stderr.log
stderr_logfile_maxbytes=0
exitcodes=0

php.ini

F├╝r Swoole ist es notwendig die Swoole-Extension in der php.ini Datei zu laden:

[PHP]
post_max_size=512M
upload_max_filesize=512M
variables_order=EGPCS
max_execution_time=240
memory_limit = 512M

[opcache]
opcache.enable=1
opcache.revalidate_freq=0
opcache.validate_timestamps=1
opcache.max_accelerated_files=30000
opcache.memory_consumption=256
opcache.max_wasted_percentage=10
opcache.interned_strings_buffer=16
opcache.fast_shutdown=1
opcache.jit_buffer_size=100M
opcache.jit=1255

extension=swoole.so

Nginx.conf

Auch hier┬áunterscheidet die Nginx-Konfiguration zwischen statischem Content, PHP-Seiten und abh├Ąngig von einem bestimmten Cookie, ob statische gecachte Files verwendet werden sollen, oder die Anfrage zu Swoole geschickt werden soll. F├╝r das Caching der statischen Files verwende ich das Laravel-Paket page-cache┬á(siehe auch:┬áJosephSilber/page-cache┬áund┬áWebseite Stresstest - Performance messen Anfragen/Sekunde).┬á

[+]
daemon off;
user root;

#pid        logs/nginx.pid;
events {
    worker_connections  1024;
}

error_log /dev/stdout info;

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    sendfile on;
    keepalive_timeout  65;
    gzip  on;
    gzip_vary on;
    gzip_min_length 10240;
    gzip_proxied any;
    gzip_disable msie6;
    gzip_comp_level 1;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;


    error_log /dev/stdout;

    server {
        listen 80 ;

        server_name _;

        auth_basic           "Test Area";
        auth_basic_user_file /var/www/.htpasswd;

        root /var/www/public;

        #redirect index.php
        if ($request_uri ~* "^/index\.php/(.*)") {
            return 301 /$1;
        }

        location ~* ^/storage/.*\.(js|css|png|jpg|jpeg|gif|svg|ico)$ {
            expires 7d;
            add_header Cache-Control "public, no-transform";
        }

        #set variables for Cache...
        set $shouldusecache4root @usecache4root;
        set $shouldusecache4pages @usecache4pages;
        if ($http_cookie ~* "nocache=YES(?:;|$)") {
            set $shouldusecache4root @nocache4root;
            set $shouldusecache4pages @nocache4pages;
        }
        if ($query_string != "") {
            set $shouldusecache4root @nocache4root;
            set $shouldusecache4pages @nocache4pages;
        }

        #hack locations...
        location = / {
            try_files /dev/null $shouldusecache4root;
        }
        location / {
            try_files /dev/null $shouldusecache4pages;
        }
        #use named locations from hacked locations...
        location @usecache4root {
            try_files /page-cache/pc__index__pc.html @swoole;
        }
        location @nocache4root {
            try_files $uri @swoole;
        }

        location @usecache4pages {
            try_files $uri $uri/ /page-cache/$uri.html /page-cache/$uri.json @swoole;
        }
        location @nocache4pages {
            try_files $uri $uri/ @swoole;
        }
        
        location @swoole {
            set $suffix "";
            if ($uri = /index.php) {
                set $suffix ?$query_string;
            }
            proxy_http_version 1.1;
            proxy_connect_timeout 60s;
            proxy_send_timeout 60s;
            proxy_read_timeout 120s;
            proxy_set_header Connection "keep-alive";
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Real-PORT $remote_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_set_header Scheme $scheme;
            proxy_set_header Server-Protocol $server_protocol;
            proxy_set_header Server-Name $server_name;
            proxy_set_header Server-Addr $server_addr;
            proxy_set_header Server-Port $server_port;
            proxy_set_header X-Requested-With $http_x_requested_with;
            proxy_pass http://127.0.0.1:8000$suffix;
            proxy_cookie_path / /;
        }

    }
}

Server Tuning

Neben der eigentlichen Docker-Installation habe ich folgendes am Webserver ge├Ąndert:

Testumgebung

Um mit Docker eine Testumgebung zu erstellen, kann die Konfiguration zus├Ątzlich auf demselben oder auf einem anderen Host mit einem alternativen DNS-Namen betrieben werden. Nachdem die Abh├Ąngigkeiten und Pakete im Docker-Container hinterlegt sind, ist die Testumgebung nicht nur ├Ąhnlich, sondern identisch zum produktiven Webserver.

Andere Beitr├Ąge zu Docker, siehe: /topic/docker

positive Bewertung({{pro_count}})
Beitrag bewerten:
{{percentage}} % positiv
negative Bewertung({{con_count}})

DANKE f├╝r deine Bewertung!

Aktualisiert: 10.08.2022 von Bernhard ­čöö


Top-Artikel in diesem Bereich


ioBroker installieren - Docker
Mit ioBroker k├Ânnen verschiedene Automatisierungsl├Âsungen oder Ger├Ąte in einem System zusammengefasst werden. Um bestimmte Gateways oder Ger├Ąte ansprechen zu k├Ânnen, werden in ioBroker verschiedene Adapter verwendet.

Home-Assistant Docker Conbee 2 und Zigbee2MQTT / deCONZ
Dank zahlreicher Integrationsm├Âglichkeiten ist Home-Assistant eine einfache Plattform f├╝r das Steuern verschiedenster Smart-Home Ger├Ąte. Im Vergleich zu ioBroker ist mir der Start mit Home Assistant wesentlich einfacher gefallen. W├Ąhrend ich f├╝r ioBroker noch am Suchen war, welches Frontend ich f├╝r meine Dashboards verwenden k├Ânnte, hatte ich mit Home-Assistant out of the box ein fertig eingerichtetes System. Die Lovelance Dashboards von Home Assistant k├Ânnen einfach in der GUI zusammengeklickt...

Conbee 2: Phoscon deCONZ - Docker Inbetriebnahme | Review
Mit dem kleinen USB-Stick Conbee2 habe ich meinen NAS um ein Zigbee-Gateway erweitert. Conbee2 kann auf Raspbian, Ubuntu, Docker oder Windows installiert werden.

Fragen / Kommentare


Durch die weitere Nutzung der Seite stimmst du der Verwendung von Cookies zu Mehr Details