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_ENV
kann 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.php
gibt einen für die Live-Umgebung spezifischen Inhalt aus - Die Route
/preview
führt einen Redirect zu der Preview-Umgebung (preview.subroot-test.tetilla.dado.opsserver.ch
) aus - Die Route
/preview/test.php
führt einen Redirect zu der/test.php
Route 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_ENV
kann 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.php
gibt 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 test
In 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 preview
Inhalt
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.ch
verlangt keine auth - Zugriff auf
https://subroot-test.tetilla.dado.opsserver.ch
zeigt Inhalt von~/www/subroot-test.tetilla.dado.opsserver.ch/index.php
an - Zugriff auf
https://subroot-test.tetilla.dado.opsserver.ch/test.php
zeigt Inhalt von~/www/subroot-test.tetilla.dado.opsserver.ch/test.php
an APP_ENV
aus~/www/subroot-test.tetilla.dado.opsserver.ch/.htaccess
wird in~/www/subroot-test.tetilla.dado.opsserver.ch/index.php
richtig geladen- Redirect von
https://subroot-test.tetilla.dado.opsserver.ch/preview
aufhttps://preview.subroot-test.tetilla.dado.opsserver.ch/
funktioniert - Redirect von
https://subroot-test.tetilla.dado.opsserver.ch/preview/test.php
aufhttps://preview.subroot-test.tetilla.dado.opsserver.ch/test.php
funktioniert
Zweiter Subroot
Hostname: preview.subroot-test.tetilla.dado.opsserver.ch
- Zugriff auf
https://preview.subroot-test.tetilla.dado.opsserver.ch
verlangt basic auth - Zugriff auf
https://preview.subroot-test.tetilla.dado.opsserver.ch
zeigt Inhalt von~/www/preview.subroot-test.tetilla.dado.opsserver.ch/index.php
an - Zugriff auf
https://preview.subroot-test.tetilla.dado.opsserver.ch/test.php
zeigt Inhalt von~/www/preview.subroot-test.tetilla.dado.opsserver.ch/test.php
an APP_ENV
aus~/www/preview.subroot-test.tetilla.dado.opsserver.ch/.htaccess
wird in~/www/preview.subroot-test.tetilla.dado.opsserver.ch/index.php
richtig geladen- Redirect von
https://preview.subroot-test.tetilla.dado.opsserver.ch/preview
aufhttps://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.