Subroot-Lösung: Gleiche Website, mehrere Umgebungen
Bisher verwendeten unsere Kunden für ihre Staging- und Prod-Umgebungen jeweils zwei verschiedene Websites. Vor Kurzem aber erhielten wir erstmals die Anfrage, ob und wie die Staging- und Prod-Umgebung auch auf der gleichen Website betrieben werden können.
Ja! Das ist möglich und benötigt abgesehen von etwas Apache Config Syntax auch keine weitere Magie. 🪄
In diesem Beispiel behandeln wir folgende zwei Use-Cases:
- die gleiche Datenbank für zwei verschiedene Umgebungen benutzen
- unterschiedliche Inhalte basierend auf verschiedenen Environment-Variablen anzeigen
Website erstellen
Zuerst muss dafür auf einem Managed Server eine neue Website erstellt werden, in diesem Fall vom Typ PHP.

01
—
01
Im Beispiel verwenden wir 3 Hostnames:
subroot-test.tetilla.dado.opsserver.ch← Erste Website und Fallbackpreview.subroot-test.tetilla.dado.opsserver.ch← Zweite Websitenot-assigned.subroot-test.tetilla.dado.opsserver.ch← Domain zeigt auf den Server, aber es existiert kein entsprechender Subroot
Damit überprüft werden kann, ob die Environment-Variablen auch korrekt geteilt werden, aktivieren wir noch eine MySQL Datenbank.

01
—
01
Ordnerstruktur
Auf der neu erstellten Website müssen zwei Ordner innerhalb des ~/www-Ordner erstellt werden:
~/www/subroot-test.tetilla.dado.opsserver.ch~/www/preview.subroot-test.tetilla.dado.opsserver.ch
Die Dateistruktur sollte aktuell also folgendermassen aussehen:

01
—
01
Dateien im Hauptordner
index.php
In der ~/www/index.php Datei legen wir nur einen kleinen Text an, um zu testen, ob jemand fälschlicherweise auf der Standardwebsite landet:
<?php
echo '<p>No one should see this</p>';
?>.htaccess
Die Logik für die richtige Handhabung der Subroots wird in der ~/www/.htaccess Datei erfasst:
# Apache configuration
# feel free to adjust this file to your needs
##################
# Subsite Config #
##################
# Enable rewrite engine
RewriteEngine On
# Define the fallback site (name of the folder)
SetEnv FALLBACK_SITE subroot-test.tetilla.dado.opsserver.ch
###
# Rewrite request to appropriate subsite folder
# name within "~/www/". Must match HTTP host header.
# Only redirect if subroot directory exists.
###
# Prevent redirect loops
RewriteCond %{ENV:REDIRECT_STATUS} ^$
# Check if the requested directory exists
RewriteCond %{DOCUMENT_ROOT}/%{HTTP_HOST} -d
# Load content from the specified subdirectory
RewriteRule ^(.*)$ /%{HTTP_HOST}/$1 [L]
###
# Redirect to Fallback if subroot doesn't exits
###
# Prevent redirect loops
RewriteCond %{ENV:REDIRECT_STATUS} ^$
# Only apply redirect rule if the folder does not exist
RewriteCond %{DOCUMENT_ROOT}/%{HTTP_HOST} !-d
# Load content from the defined fallback site
RewriteRule ^(.*)$ /%{ENV:FALLBACK_SITE}/$1 [L]
#########
# Other #
#########
# recommended security headers
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=63072000"Durch den in der FALLBACK_SITE-Environment-Variablen erfassten Ordner wird definiert, was mit den Requests von einer Domain passieren soll, für die kein Subroot existiert. In unserem Fall wird nun bei Requests an not-assigned.subroot-test.tetilla.dado.opsserver.ch der Inhalt von ~/www/subroot-test.tetilla.dado.opsserver.ch geladen.
Falls statt Anzeige des Inhalts eines Subroots ein Redirect zu einem anderen Subroot gewünscht ist, muss eine Zeile der Konfiguration angepasst werden:
-RewriteRule ^(.*)$ /%{ENV:FALLBACK_SITE}/$1 [L]
+RewriteRule ^(.*)$ http://%{ENV:FALLBACK_SITE}/$1 [R=302,L]Erster Subroot: subroot-test.tetilla.dado.opsserver.ch
Im ersten Subroot kann nun das gewünschte Projekt angelegt werden und mit einer entsprechenden .htaccess-Datei eine Konfiguration angewendet werden.
Wir möchten folgendes Verhalten des Subroots erzeugen:
- Die Environment-Variable
APP_ENVkann von der PHP-Applikation ausgelesen werden und beinhaltet den Wertlive - Die Route
/gibt einen für die Live-Umgebung spezifischen Inhalt aus, die Environment-Variablen für die Datenbank-Konfiguration sind jedoch identisch mit dem zweiten Subroot - Die Route
/test.phpgibt einen für die Live-Umgebung spezifischen Inhalt aus - Die Route
/previewführt einen Redirect zu der Preview-Umgebung (preview.subroot-test.tetilla.dado.opsserver.ch) aus - Die Route
/preview/test.phpführt einen Redirect zu der/test.phpRoute der Preview-Umgebung (preview.subroot-test.tetilla.dado.opsserver.ch) aus
Webserver Konfiguration
In der Datei ~/www/subroot-test.tetilla.dado.opsserver.ch/.htaccess konfigurieren wir die Subroot spezifische Environment-Variable und den /preview Redirect:
RewriteEngine On
SetEnv APP_ENV live
RewriteRule ^preview(.*)$ https://preview.subroot-test.tetilla.dado.opsserver.ch$1 [R=302,L]Inhalt
Für die ~/www/subroot-test.tetilla.dado.opsserver.ch/index.php-Datei definieren wir einen Inhalt, der es uns ermöglicht, Environment-Variablen auf Website- und Subroot-Ebene zu vergleichen:
<?php
echo '<h1>LIVE</h1>';
echo '<p>APP_ENV: '.$_SERVER['APP_ENV'].'</p>';
echo '<p>DB_HOST: '.$_SERVER['DB_HOST'].'</p>';
echo '<p>DB_DATABASE: '.$_SERVER['DB_NAME'].'</p>';
echo '<p>DB_USERNAME: '.$_SERVER['DB_USERNAME'].'</p>';
?>Ebenso muss die ~/www/subroot-test.tetilla.dado.opsserver.ch/test.php-Datei erstellt werden, hier wird ebenfalls ein für die Live-Umgebung spezifischer Inhalt erfasst:
<?php
echo 'Test on Live';
?>Zweiter Subroot: preview.subroot-test.tetilla.dado.opsserver.ch
- Die Environment-Variable
APP_ENVkann von der PHP-Applikation ausgelesen werden und beinhaltet den Wertpreview - Die Route
/gibt einen für die Preview-Umgebung spezifischen Inhalt aus, die Environment-Variablen für die Datenbank-Konfiguration sind jedoch identisch mit dem ersten Subroot - Die Route
/test.phpgibt einen für die Preview-Umgebung spezifischen Inhalt aus - Alle Routes in der Preview-Umgebung sind durch Basic-Auth geschützt
Webserver Konfiguration
Zuerst muss eine .htpasswd-Datei mit entsprechendem Passwort generiert werden. Im Beispiel wird ein User test mit dem Passwort password erstellt.
# subroot-test@tetilla.dado.opsserver.ch in ~ on git:main o disk:80% [12:58:05] C:2
$ htpasswd -c ~/cnf/.htpasswd test
New password:
Re-type new password:
Adding password for user testIn der Datei ~/www/preview.subroot-test.tetilla.dado.opsserver.ch/.htaccess konfigurieren wir die Subroot-spezifische Environment-Variable und die Basic Authentication:
# Credentials:
# ==================
# Username: test
# Password: password
AuthType basic
AuthName "Preview Area"
AuthUserFile /home/subroot-test/cnf/.htpasswd
Require valid-user
SetEnv APP_ENV previewInhalt
Für die ~/www/preview.subroot-test.tetilla.dado.opsserver.ch/index.php-Datei definieren wir einen ähnlichen Inhalt wie im ersten Subroot, der es uns ermöglicht, Environment-Variablen auf Website- und Subroot-Ebene zu vergleichen:
<?php
echo '<h1>PREVIEW</h1>';
echo '<p>APP_ENV: '.$_SERVER['APP_ENV'].'</p>';
echo '<p>DB_HOST: '.$_SERVER['DB_HOST'].'</p>';
echo '<p>DB_DATABASE: '.$_SERVER['DB_NAME'].'</p>';
echo '<p>DB_USERNAME: '.$_SERVER['DB_USERNAME'].'</p>';
?>Ebenso erstellen wir ein Äquivalent zur test.php-Datei im ersten Subroot, ~/www/preview.subroot-test.tetilla.dado.opsserver.ch/test.php:
?>
<?php
echo 'Test on preview';
?>Finale Dateistruktur
Nun sollte unsere Dateistruktur folgendermassen aussehen:

01
—
01
Überprüfen
Jetzt können wir für die Tests die Password Protection der Website ausschalten:

01
—
01
Erster Subroot
Hostname: subroot-test.tetilla.dado.opsserver.ch
- Zugriff auf
https://subroot-test.tetilla.dado.opsserver.chverlangt keine auth - Zugriff auf
https://subroot-test.tetilla.dado.opsserver.chzeigt Inhalt von~/www/subroot-test.tetilla.dado.opsserver.ch/index.phpan - Zugriff auf
https://subroot-test.tetilla.dado.opsserver.ch/test.phpzeigt Inhalt von~/www/subroot-test.tetilla.dado.opsserver.ch/test.phpan APP_ENVaus~/www/subroot-test.tetilla.dado.opsserver.ch/.htaccesswird in~/www/subroot-test.tetilla.dado.opsserver.ch/index.phprichtig geladen- Redirect von
https://subroot-test.tetilla.dado.opsserver.ch/previewaufhttps://preview.subroot-test.tetilla.dado.opsserver.ch/funktioniert - Redirect von
https://subroot-test.tetilla.dado.opsserver.ch/preview/test.phpaufhttps://preview.subroot-test.tetilla.dado.opsserver.ch/test.phpfunktioniert
Zweiter Subroot
Hostname: preview.subroot-test.tetilla.dado.opsserver.ch
- Zugriff auf
https://preview.subroot-test.tetilla.dado.opsserver.chverlangt basic auth - Zugriff auf
https://preview.subroot-test.tetilla.dado.opsserver.chzeigt Inhalt von~/www/preview.subroot-test.tetilla.dado.opsserver.ch/index.phpan - Zugriff auf
https://preview.subroot-test.tetilla.dado.opsserver.ch/test.phpzeigt Inhalt von~/www/preview.subroot-test.tetilla.dado.opsserver.ch/test.phpan APP_ENVaus~/www/preview.subroot-test.tetilla.dado.opsserver.ch/.htaccesswird in~/www/preview.subroot-test.tetilla.dado.opsserver.ch/index.phprichtig geladen- Redirect von
https://preview.subroot-test.tetilla.dado.opsserver.ch/previewaufhttps://preview.subroot-test.tetilla.dado.opsserver.ch/funktioniert nicht
Nicht existierende Subroots
Hier soll alles wie auf der konfigurierten FALLBACK_SITE funktionieren, es soll aber der originale Hostname not-assigned.subroot-test.tetilla.dado.opsserver.ch verwendet werden.
Falls ein Redirect zur FALLBACK_SITE konfiguriert wurde, soll dieser erfolgen.
