Infomaniak vps kundenwebsites

Lehre für künftige Updates: Bei Image-Updates immer den ganzen Stack zusammen recreaten, z.B. docker compose up -d --force-recreate statt nur PHP. Oder im Workflow nach wordpress-image-Rebuild zusätzlich docker restart <site>-nginx pro Site.

1. Filesystem-Layout

Bash
/mnt/data/                           alles auf separater Disk (gut!)

├── docker/                          Docker daemon data-root (volumes, images)
   └── volumes/
       ├── nginx-proxy_certs/       Let's Encrypt Zertifikate (zentral)
│       ├── nginx-proxy_vhost/      ← per-vhost nginx-Snippets
│       ├── nginx-proxy_html/       ← ACME http-01 challenges
│       ├── fastpage_wp_data/       ← WordPress files (wp-content, core, …)
│       ├── fastpage_db_data/       ← MariaDB datadir
│       ├── fastpage_nginx_cache/   ← FastCGI-Cache
│       └── …gleiches Muster pro Site

├── sites/                          ← pro Site ein Verzeichnis
│   ├── fastpage/
│   │   ├── docker-compose.yml      ← Stack-Definition
│   │   ├── .env             (600)  ← DB_PASS, DB_ROOT_PASS
│   │   ├── .credentials     (600)  ← Admin-PW, DB-Creds (Klartext!)
│   │   └── config/
│   │       ├── nginx.conf          ← site-spezifische server{}
│   │       └── php.ini             ← upload-size, opcache, …
│   ├── buezerpage/                 ← gleiche Struktur
│   ├── gwdev/
│   ├── test/  test2/  test3-gwdev/
│   └── (static-sites haben zusätzlich:)
│       ├── public/                 ← Web-Root (HTML/PHP/CSS)
│       └── config/msmtprc   (600)  ← SMTP-Creds

├── logs/                           ← auf Host, damit fail2ban liest
│   └── <site>/nginx/
│       ├── access.log
│       └── error.log

├── nginx-proxy/                    ← der Reverse-Proxy-Stack
│   └── docker-compose.yml

├── nginx-image/                    ← Build-Context: gw/nginx:cache-purge
│   ├── Dockerfile                  ← alpine + nginx 1.26.2 + ngx_cache_purge
│   └── nginx.conf                  ← http{} mit FastCGI-Cache-Zone

├── wordpress-image/                ← Build-Context: gw/wordpress:php8.3-fpm
│   └── Dockerfile                  ← wordpress:php8.3-fpm + wp-cli

├── php-static-image/               ← Build-Context: gw/php:static-mail
│   ├── Dockerfile                  ← php:8.3-fpm-alpine + msmtp + opcache
│   └── sendmail.ini

├── fail2ban-config/                ← Host-fail2ban configs
│   ├── wordpress-login.conf
│   ├── wordpress-xmlrpc.conf
│   ├── wordpress.local
│   ├── docker-user.conf
│   └── logrotate-nginx

├── new-site.sh                     ← Provisioner: WordPress-Stack
└── new-static-site.sh              ← Provisioner: Static/PHP-Stack

2. Netzwerk-Topologie (Container-Ebene)

Bash
                          INTERNET
                              
                       ┌──────┴──────┐
                         Ports 80,  
                            443         einzige offene Ports
                       └──────┬──────┘
                              
              ╔═══════════════▼═══════════════════╗
                 Docker-Netz: nginx-proxy_default 
                                                  
                ┌───────────────────────────┐    
                   nginx-proxy                    liest VIRTUAL_HOST
                   (nginxproxy/nginx-proxy)│◄───╫── aus Container-Envs
                                                  via docker.sock (ro)
                   + acme-companion                generiert vhosts
                     (Let's Encrypt)       │    ║   → holt Certs
              ║  └───────────┬───────────────┘    ║
              ║              │                    ║
              ║       (HTTP, intern)              ║
              ║              │                    ║
              ║  ┌───────────┼─────────────────┐  ║
              ║  │           │                 │  ║
              ║  ▼           ▼                 ▼  ║
              ║ fastpage-  buezerpage-  …gwdev-   ║
              ║ nginx      nginx        nginx     ║
              ╚══╪═══════════╪═════════════╪══════╝
                 │           │             │
       ╔═════════╪═══╗ ╔═════╪══════╗ ╔════╪═══════╗
       ║ fastpage_   ║ ║ buezerpage_║ ║ gwdev_     ║
       ║ internal    ║ ║ internal   ║ ║ internal   ║   ← isolierte
       ║             ║ ║            ║ ║            ║     Netze pro Site
       ║ ┌─────────┐ ║ ║ ┌────────┐ ║ ║ ┌────────┐ ║
       ║ │  php    │ ║ ║ │  php   │ ║ ║ │  php   │ ║
       ║ │ (fpm)   │ ║ ║ │        │ ║ ║ │        │ ║
       ║ ├─────────┤ ║ ║ ├────────┤ ║ ║ ├────────┤ ║
       ║ │  db     │ ║ ║ │  db    │ ║ ║ │  db    │ ║   ← keine Ports
       ║ │(mariadb)│ ║ ║ │        │ ║ ║ │        │ ║     nach aussen
       ║ ├─────────┤ ║ ║ ├────────┤ ║ ║ ├────────┤ ║
       ║ │ redis   │ ║ ║ │ redis  │ ║ ║ │ redis  │ ║
       ║ └─────────┘ ║ ║ └────────┘ ║ ║ └────────┘ ║
       ╚═════════════╝ ╚════════════╝ ╚════════════╝

       (analog für test, test2, test3-gwdev)

Wichtige Eigenschaft: Jeder nginx-Container hängt in zwei Netzen:

  • <site>_internal → erreicht php, db, redis (private)
  • nginx-proxy_default → erreichbar vom Reverse Proxy

php/db/redis hängen nur im internen Netz. Damit kann keine Site die DB einer anderen sehen.

3. Request-Flow: ein HTTP-Request

Bash
  User Browser
       
         GET https://fastpage.ch/
       
┌────────────────────────────────────────┐
  nginx-proxy  (Container)              │
  ─────────────────────────             
   TLS-Terminierung (Cert aus          
    nginx-proxy_certs Volume)           │
   Routing nach Host-Header:           
    fastpage.ch  fastpage-nginx        
   setzt X-Forwarded-For, X-Real-IP    
└──────────────────┬─────────────────────┘
                     HTTP (plain) im
                     nginx-proxy_default Netz
                   
┌────────────────────────────────────────┐
  fastpage-nginx  (Container)           │
  ─────────────────────────             
   real_ip_header X-Forwarded-For      
   Security-Header, Cache-Control      
   try_files: statisch direkt          
   WebP/AVIF-Rewrite                   
   Skip-Cache-Logik (POST, wp-admin,   
    logged-in cookies)                  │
   FastCGI-Cache HIT?  direkt antw.   
└────┬───────────────────────┬───────────┘
      (HIT)                 │ (MISS)
                            
                   ┌────────────────────┐
                    fastpage-php (fpm) │
                     ────────────────  
                      WordPress       
                      WP_REDIS_HOST=  
                       redis           
                   └───┬──────────┬─────┘
                                 
                                 
                ┌──────────┐ ┌──────────┐
                 db         redis    
                (mariadb)   (object  
                             cache)  │
                └──────────┘ └──────────┘
                       
                        Response gerendert
                         in nginx-Cache geschrieben
                       
   Antwort an nginx-proxy  TLS  Browser

Logs:
   fastpage-nginx  /var/log/nginx/{access,error}.log
       (Bind-Mount auf /mnt/data/logs/fastpage/nginx/)
                              
                              
                  ┌────────────────────────┐
                   Host: fail2ban         
                    ─────────────         
                   Jails:                 
                     wordpress-login     
                     wordpress-xmlrpc    
                    IP-Ban via iptables  
                  └────────────────────────┘

4. Provisioning-Flow: new-site.sh

Bash
  Operator: bash /mnt/data/new-site.sh
       
       ├─► fragt: SITE_NAME, DOMAIN, ADMIN_EMAIL
       
       ├─► Validierung (regex, kein Overwrite)
       
       ├─► generiert: DB_PASS, DB_ROOT_PASS, WP_PASS  (openssl rand)
       
       ├─► mkdir /mnt/data/sites/<name>/{config,}
              /mnt/data/logs/<name>/nginx/
       
       ├─► schreibt heredocs:
            .env                 (chmod 600)
            docker-compose.yml
            config/nginx.conf
            config/php.ini
       
       ├─► docker compose up -d
            ├─ pullt mariadb:10.11, redis:alpine
            └─ verwendet lokale Images gw/wordpress:php8.3-fpm,
                                     gw/nginx:cache-purge
       
       ├─► wartet bis /var/www/html/wp-config.php existiert
          (WordPress-Entrypoint kopiert Core + erzeugt config)
       
       ├─► WP-CLI Bootstrap (~25 wp Commands):
             wp core install (mit ADMIN_EMAIL, WP_PASS)
             Sprache de_DE, Timezone Europe/Zurich
             Comments aus, Avatars aus
             Permalinks /%postname%/
             Dummy-Posts/Themes/Plugins löschen
             redis-cache + nginx-helper installieren & aktivieren
             webp-converter-for-media installieren (nicht aktivieren)
       
       ├─► nginx-proxy bemerkt den neuen Container via docker.sock
              schreibt vhost-Snippet
              reloaded
          acme-companion bemerkt LETSENCRYPT_HOST
              holt Cert via HTTP-01 challenge
       
       ├─► schreibt .credentials  (chmod 600)
       
       └─► gibt Login-URL + PW aus

Bei Fehler greift der ERR/INT/TERM-Trap: docker compose down -v + rm -rf $SITE_DIR. Kein Müll bleibt liegen.

5. Image-Hierarchie

Bash
   alpine:3.19
        
        └──► gw/nginx:cache-purge          (lokal gebaut)
                 nginx 1.26.2
                 ngx_cache_purge 2.3
                 http{}-Block mit FastCGI-Cache-Zone "WORDPRESS"

   wordpress:php8.3-fpm  (upstream)
        
        └──► gw/wordpress:php8.3-fpm       (lokal gebaut)
                 + wp-cli

   php:8.3-fpm-alpine    (upstream)
        
        └──► gw/php:static-mail            (lokal gebaut)
                 + msmtp (als sendmail-Replacement)
                 + opcache
                 sendmail.ini


   Verwendung:
   ─────────────────────────────────────────────────────
   WordPress-Sites:    gw/wordpress:php8.3-fpm
                     + gw/nginx:cache-purge
                     + mariadb:10.11
                     + redis:alpine

   Static/PHP-Sites:   gw/php:static-mail
                     + gw/nginx:cache-purge
                     (keine DB, kein Redis)

   Reverse Proxy:      nginxproxy/nginx-proxy
                     + nginxproxy/acme-companion

6. Volume-Mounts pro WordPress-Site (Detail)

Bash
   Container: fastpage-nginx
   ─────────────────────────
   fastpage_wp_data          /var/www/html              (RO)  ← PHP-Files lesen
   ./config/nginx.conf       /etc/nginx/conf.d/default.conf
   fastpage_nginx_cache      /var/cache/nginx                   FastCGI-Cache
   /mnt/data/logs/fastpage/  /var/log/nginx                     für fail2ban

   Container: fastpage-php
   ─────────────────────────
   fastpage_wp_data          /var/www/html              (RW)  ← Uploads schreiben
   ./config/php.ini          /usr/local/etc/php/conf.d/custom.ini

   Container: fastpage-db
   ─────────────────────────
   fastpage_db_data          /var/lib/mysql

   Container: fastpage-redis
   ─────────────────────────
   (kein Volume  Cache, darf weg)

Schlüssel-Idee: wp_data ist shared zwischen php (rw) und nginx (ro). Nginx serviert statisch direkt; PHP-Requests gehen via FastCGI an php:9000.

7. Wer-spricht-mit-wem-Matrix

Von ↓ / Nach →nginx-proxysite-nginxsite-phpsite-dbsite-redisHost
Internet✅ :80/:443
nginx-proxy✅ :80docker.sock (ro)
site-nginx✅ :9000logs (bind)
site-php✅ :3306✅ :6379
andere site