diff --git a/.env b/.env
new file mode 100644
index 00000000..9bae5849
--- /dev/null
+++ b/.env
@@ -0,0 +1,3 @@
+SINGULARITY_HOSTNAME=localhost
+SINGULARITY_VERSION="v0.1"
+SINGULARITY_DEPLOYMENT_STATUS="(in development)"
diff --git a/container-compose-dev.yml b/container-compose-dev.yml
index ff4e0457..9b48fd88 100644
--- a/container-compose-dev.yml
+++ b/container-compose-dev.yml
@@ -1,57 +1,17 @@
 services:
   nginx:
-    build:
-      context: extenginx
-      additional_contexts:
-        - nginx_snippet_source=./nginx_snippets
-        - nginx_webroot_content=./kdlp.underground.software
-      dockerfile: Containerfile
-      target: nginx
-      args:
-        NGINX_HOSTNAME: localhost
-        NGINX_HTTPS_LISTEN: 'unix:/run/nginx/socks/https.sock'
-        NGINX_SMTPS_LISTEN: 'unix:/run/nginx/socks/smtps.sock'
-        NGINX_POP3S_LISTEN: 'unix:/run/nginx/socks/pop3s.sock'
     security_opt:
       - label:disable
     volumes:
-      - type: bind
-        source: ./socks
-        target: /run/nginx/socks
-        read_only: false
-        selinux: z
-      - type: volume
-        source: ssl-certs
-        target: /etc/ssl/nginx
-        read_only: true
       - type: bind
         source: ./nginx_snippets
         target: /etc/nginx/include.d
         read_only: true
-        selinux: z
       - type: bind
         source: ./kdlp.underground.software
         target: /var/www/html
         read_only: true
-        selinux: z
-    depends_on:
-      - orbit
-      - smtp
-      - pop
-    networks:
-      - orbit
-      - smtp
-      - pop
   orbit:
-    build:
-      context: orbit
-      dockerfile: Containerfile
-      additional_contexts:
-        - orbit_singularity_git_dir=./.git
-        - orbit_docs_source=./kdlp.underground.software
-      target: orbit
-      args:
-        orbit_version_info: "singularity v0.1 (in development) https://github.com/underground-software/singularity"
     security_opt:
       - label:disable
     volumes:
@@ -59,53 +19,3 @@ services:
         source: ./kdlp.underground.software
         target: /orbit/docs
         read_only: true
-        selinux: z
-      - type: volume
-        source: orbit-db
-        target: /var/orbit
-        read_only: false
-    networks:
-      - orbit
-  smtp:
-    build:
-      context: smtp
-      dockerfile: Containerfile
-      additional_contexts:
-        - tcp_server_source=./tcp_server
-      target: smtp
-      args:
-        hostname: localhost
-        LISTEN_PORT: 1465
-    volumes:
-      - type: volume
-        source: email
-        target: /mnt/email_data
-        read_only: false
-    networks:
-      - smtp
-  pop:
-    build:
-      context: pop
-      dockerfile: Containerfile
-      additional_contexts:
-        - tcp_server_source=./tcp_server
-      target: pop
-      args:
-        LISTEN_PORT: 1995
-    volumes:
-      - type: volume
-        source: email
-        target: /mnt/email_data
-        read_only: true
-    depends_on:
-      - smtp
-    networks:
-      - pop
-networks:
-  orbit:
-  smtp:
-  pop:
-volumes:
-  ssl-certs:
-  email:
-  orbit-db:
diff --git a/container-compose-staging.yml b/container-compose-staging.yml
deleted file mode 100644
index 178f6a0d..00000000
--- a/container-compose-staging.yml
+++ /dev/null
@@ -1,92 +0,0 @@
-services:
-  nginx:
-    build:
-      context: extenginx
-      additional_contexts:
-        - nginx_snippet_source=./nginx_snippets
-        - nginx_webroot_content=./kdlp.underground.software
-      dockerfile: Containerfile
-      target: nginx
-      args:
-        NGINX_HOSTNAME: dev.underground.software
-        NGINX_HTTPS_LISTEN: 'unix:/run/nginx/socks/https.sock'
-        NGINX_SMTPS_LISTEN: 'unix:/run/nginx/socks/smtps.sock'
-        NGINX_POP3S_LISTEN: 'unix:/run/nginx/socks/pop3s.sock'
-    volumes:
-      - type: bind
-        source: ./socks
-        target: /run/nginx/socks
-        read_only: false
-        selinux: z
-      - type: volume
-        source: ssl-certs
-        target: /etc/ssl/nginx
-        read_only: true
-    depends_on:
-      - orbit
-      - smtp
-      - pop
-    networks:
-      - orbit
-      - smtp
-      - pop
-  orbit:
-    build:
-      context: orbit
-      dockerfile: Containerfile
-      additional_contexts:
-        - orbit_singularity_git_dir=./.git
-        - orbit_docs_source=./kdlp.underground.software
-      target: orbit
-      args:
-        orbit_version_info: "singularity v0.1 (staging) https://github.com/underground-software/singularity"
-    volumes:
-      - type: volume
-        source: orbit-db
-        target: /var/orbit
-        read_only: false
-    networks:
-      - orbit
-  smtp:
-    build:
-      context: smtp
-      dockerfile: Containerfile
-      additional_contexts:
-        - tcp_server_source=./tcp_server
-      target: smtp
-      args:
-        hostname: dev.underground.software
-        LISTEN_PORT: 1465
-    volumes:
-      - type: volume
-        source: email
-        target: /mnt/email_data
-        read_only: false
-    networks:
-      - smtp
-  pop:
-    build:
-      context: pop
-      dockerfile: Containerfile
-      additional_contexts:
-        - tcp_server_source=./tcp_server
-      target: pop
-      args:
-        LISTEN_PORT: 1995
-    volumes:
-      - type: volume
-        source: email
-        target: /mnt/email_data
-        read_only: true
-    depends_on:
-      - smtp
-    networks:
-      - pop
-networks:
-  orbit:
-  smtp:
-  pop:
-volumes:
-  ssl-certs:
-  email:
-  orbit-db:
diff --git a/container-compose.yml b/container-compose.yml
index bc6453e8..1edbfbba 100644
--- a/container-compose.yml
+++ b/container-compose.yml
@@ -8,7 +8,7 @@ services:
       dockerfile: Containerfile
       target: nginx
       args:
-        NGINX_HOSTNAME: kdlp.underground.software
+        NGINX_HOSTNAME: ${SINGULARITY_HOSTNAME}
         NGINX_HTTPS_LISTEN: 'unix:/run/nginx/socks/https.sock'
         NGINX_SMTPS_LISTEN: 'unix:/run/nginx/socks/smtps.sock'
         NGINX_POP3S_LISTEN: 'unix:/run/nginx/socks/pop3s.sock'
@@ -39,7 +39,7 @@ services:
         - orbit_docs_source=./kdlp.underground.software
       target: orbit
       args:
-        orbit_version_info: "singularity v0.1 https://github.com/underground-software/singularity"
+        orbit_version_info: "singularity ${SINGULARITY_VERSION} ${SINGULARITY_DEPLOYMENT_STATUS} https://github.com/underground-software/singularity"
     volumes:
       - type: volume
         source: orbit-db
@@ -55,7 +55,7 @@ services:
         - tcp_server_source=./tcp_server
       target: smtp
       args:
-        hostname: kdlp.underground.software
+        hostname: ${SINGULARITY_HOSTNAME}
         LISTEN_PORT: 1465
     volumes:
       - type: volume
diff --git a/test.sh b/test.sh
index 2054b37c..8ba7dc0f 100755
--- a/test.sh
+++ b/test.sh
@@ -27,6 +27,7 @@ DOCKER=${DOCKER:-podman}
 
 require() { command -v "$1" > /dev/null || { echo "error: $1 command required yet absent" ; exit 1 ; } ; }
 require curl
+require jq
 require "${DOCKER}"
 
 # Check for shell script style compliance with shellcheck
@@ -43,19 +44,13 @@ mkdir -p test
 # Reset the test directory
 rm -f test/*
 
-DEVEL=${DEVEL:-""}
-STAGING=${STAGING:-""}
-EMAIL_HOSTNAME="kdlp.underground.software"
+HOSTNAME_FROM_DOTENV="$(env -i sh -c '
+set -o allexport
+. ./.env
+exec jq -r -n "env.SINGULARITY_HOSTNAME"
+')"
 
-# NOTE: don't set DEVEL and STAGING at the same time
-
-if [ -n "$DEVEL" ]; then
-	EMAIL_HOSTNAME="localhost"
-fi
-
-if [ -n "$STAGING" ]; then
-	EMAIL_HOSTNAME="dev.underground.software"
-fi
+SINGULARITY_HOSTNAME=${SINGULARITY_HOSTNAME:-"${HOSTNAME_FROM_DOTENV}"}
 
 ${DOCKER} cp singularity_nginx_1:/etc/ssl/nginx/fullchain.pem test/ca_cert.pem
 
@@ -125,7 +120,7 @@ EOF
 add_cleanup "${DOCKER} volume import singularity_orbit-db test/orbit_orig.tar"
 
 # Check that registration fails before user creation
-curl --url "https://$EMAIL_HOSTNAME/register" \
+curl --url "https://$SINGULARITY_HOSTNAME/register" \
   --unix-socket ./socks/https.sock \
   "${CURL_OPTS[@]}" \
   --data "student_id=1234" \
@@ -133,7 +128,7 @@ curl --url "https://$EMAIL_HOSTNAME/register" \
   | grep "msg = no such student"
 
 # Check that login fails before user creation
-curl --url "https://$EMAIL_HOSTNAME/login" \
+curl --url "https://$SINGULARITY_HOSTNAME/login" \
   --unix-socket ./socks/https.sock \
   "${CURL_OPTS[@]}" \
   --data "username=user&password=pass" \
@@ -152,7 +147,7 @@ add_cleanup "orbit/warpdrive.sh \
   | grep 'user'"
 
 # Check that registration fails with incorrect student id
-curl --url "https://$EMAIL_HOSTNAME/register" \
+curl --url "https://$SINGULARITY_HOSTNAME/register" \
   --unix-socket ./socks/https.sock \
   "${CURL_OPTS[@]}" \
   --data "student_id=123" \
@@ -160,7 +155,7 @@ curl --url "https://$EMAIL_HOSTNAME/register" \
   | grep "msg = no such student"
 
 # Check that registration succeeds with correct student id
-curl --url "https://$EMAIL_HOSTNAME/register" \
+curl --url "https://$SINGULARITY_HOSTNAME/register" \
   --unix-socket ./socks/https.sock \
   "${CURL_OPTS[@]}" \
   --data "student_id=1234" \
@@ -168,7 +163,7 @@ curl --url "https://$EMAIL_HOSTNAME/register" \
   | grep "msg = welcome to the classroom"
 
 # Check that registration fails when student id is used for a second time
-curl --url "https://$EMAIL_HOSTNAME/register" \
+curl --url "https://$SINGULARITY_HOSTNAME/register" \
   --unix-socket ./socks/https.sock \
   "${CURL_OPTS[@]}" \
   --data "student_id=1234" \
@@ -176,7 +171,7 @@ curl --url "https://$EMAIL_HOSTNAME/register" \
   | grep "msg = no such student"
 
 # Check that login fails when credentials are invalid
-curl --url "https://$EMAIL_HOSTNAME/login" \
+curl --url "https://$SINGULARITY_HOSTNAME/login" \
   --unix-socket ./socks/https.sock \
   "${CURL_OPTS[@]}" \
   --data "username=user&password=invalid" \
@@ -184,7 +179,7 @@ curl --url "https://$EMAIL_HOSTNAME/login" \
   | grep "msg = authentication failure"
 
 # Check that login succeeds when credentials are valid
-curl --url "https://$EMAIL_HOSTNAME/login" \
+curl --url "https://$SINGULARITY_HOSTNAME/login" \
   --unix-socket ./socks/https.sock \
   "${CURL_OPTS[@]}" \
   --data "username=user&password=pass" \
@@ -192,7 +187,7 @@ curl --url "https://$EMAIL_HOSTNAME/login" \
   | grep "msg = user authenticated by password"
 
 # Check that the user can get the empty list of email on the server
-curl --url "pop3s://$EMAIL_HOSTNAME" \
+curl --url "pop3s://$SINGULARITY_HOSTNAME" \
   --unix-socket ./socks/pop3s.sock \
   "${CURL_OPTS[@]}" \
   --user user:pass \
@@ -202,11 +197,11 @@ curl --url "pop3s://$EMAIL_HOSTNAME" \
 CR=$(printf "\r")
 # Check that the user can send a message to the server
 (
-curl --url "smtps://$EMAIL_HOSTNAME" \
+curl --url "smtps://$SINGULARITY_HOSTNAME" \
   --unix-socket ./socks/smtps.sock \
   "${CURL_OPTS[@]}" \
-  --mail-from "user@$EMAIL_HOSTNAME" \
-  --mail-rcpt "other@$EMAIL_HOSTNAME" \
+  --mail-from "user@$SINGULARITY_HOSTNAME" \
+  --mail-rcpt "other@$SINGULARITY_HOSTNAME" \
   --upload-file - \
   --user 'user:pass' <<EOF
 Subject: Message Subject$CR
@@ -222,7 +217,7 @@ EOF
 add_cleanup nuke_mail
 
 # Check that the user can get the most recent message sent to the server
-curl --url "pop3s://$EMAIL_HOSTNAME/1" \
+curl --url "pop3s://$SINGULARITY_HOSTNAME/1" \
   --unix-socket ./socks/pop3s.sock \
   "${CURL_OPTS[@]}" \
   --user user:pass \
