Initial commit

This commit is contained in:
Minecon724 2025-07-11 14:48:23 +02:00
commit 961e7aebe4
Signed by: Minecon724
GPG key ID: A02E6E67AB961189
14 changed files with 289 additions and 0 deletions

1
.dockerignore Normal file
View file

@ -0,0 +1 @@
run/

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
run/

30
Containerfile Normal file
View file

@ -0,0 +1,30 @@
# Remember to update!
FROM docker.io/nginx:1.29.0-alpine-slim
ENV DOMAIN="example.localhost"
ENV SERVER_ID="server"
ENV RELOAD_FILE="/var/run/nginx-reload"
ENV ACME_CHALLENGE_URL="http://acme-challenge.${DOMAIN}/.well-known/acme-challenge/"
RUN apk add --no-cache \
inotify-tools # For reloading
# Copy the configuration files
COPY nginx /etc/nginx/
# Copy the dummy certificate files
COPY certificates /etc/ssl/
# Copy the entrypoint scripts
COPY --chmod=0755 docker-entrypoint.d /docker-entrypoint.d/
# Copy the scripts
COPY --chmod=0755 scripts/ /opt/scripts/
RUN ln -s /opt/scripts/reload.sh /usr/local/bin/reload
# Create the volumes for certificates and website files
VOLUME /etc/ssl/certs
VOLUME /var/www/html
# Expose the ports for HTTP and HTTPS
EXPOSE 80/tcp 443/tcp 443/udp

26
README.md Normal file
View file

@ -0,0 +1,26 @@
This is a container that helps host a static website.
## Configuration
**Requires** the following mounts:
- `/etc/ssl/certs/$DOMAIN`: For certificates (`fullchain.pem` and `privkey.pem`)
- `/var/www/html/$DOMAIN`: Website files, `index.html` goes right here
**Requires** the following environment variables:
- `DOMAIN`: The domain
- `ACME_CHALLENGE_HOST`: The source of `.well-known/acme-challenge`
You're also encouraged to provide your own:
- `/etc/ssl/dhparam.pem`, generated with:
```bash
openssl dhparam -out dhparam.pem 4096
```
- `/etc/ssl/snakeoil.key` & `/etc/ssl/snakeoil.pem`, generated with:
```bash
openssl req -new -x509 -days 398 -noenc -out snakeoil.pem -keyout snakeoil.key -subj "/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd"
```
- `SERVER_ID`: How to call this server (for info)
- **Mount** `/var/run/nginx-reload`: modify this file to reload nginx
## TODO
- support for multiple domains

13
certificates/dhparam.pem Normal file
View file

@ -0,0 +1,13 @@
-----BEGIN DH PARAMETERS-----
MIICDAKCAgEAgzMa8BTPT60M95CnRmSc69n9b+hY+9T8jte8HNEQfu07sJQkKJfK
c5znc+bR1fnt0CFexz2rm4TUig082R8zEopfoXztR2XEtIEqiJ9Lt2HGx9t2GFi+
aYAvB7Y8qvFq67dJdIzqVEkjoRBZwUiOAbRuaL54LOz5pBBv0217ezzbfEYtItAu
DdOQOZEfQ1+N7PJ9QQpfDJVl7rKfzna0gF3Wox1q5QtNqks44TWYiZqs0eOWkged
18PEsrwYpeiEqz7/ytaLONB1YU2FSrnYQFE9e7iMRMddEPRoor3VKeRC7gWTNIsB
Kkt2LfDX0lXO7bEuKMEoGvt7xkJ2XlUDjtc3htF5BKcuCWpygXTBVtZ+tkdPpjYP
fp/gS2X060GPI3Egxtua2kHj3zHiebQHn000CxLgSbUUwNxzhThoMONF7v4Z3R9D
TUYLsu1No6FzQbotJiFepoSkI9WqhdMO/9VYu/EwBGq+Wlhqo9mh9KQL4uMdIC4P
v1ZoazRRn6ioaPLwabwvsZnzu+TxQBuAolDB2rN7dSljXNinG/y2Jv6y5LJZlFI9
nSVm22Sm+mwHz+rlfBmipaIxMTxN4UyxSx9oLEMj0c0PDXmbPaxJ78tw2+RG9dLn
cbXROxsE/0MehRpDM6ksn/O2XRXFeSs0ol6Z4EZ98zxUa4crDanUD0cCAQICAgFF
-----END DH PARAMETERS-----

28
certificates/snakeoil.key Normal file
View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc2YmG90vmG/Og
1hpufu81YjDaPNGOdsDcjagA0mFFs2kGATrg95JZNcu5Aef8ropEIV9mu3++zP8K
VjPD+7HJ2eCkT1tT4IAy9q4JQAEv1efyjHbcfgXAhPRhil2hSICS2lCnL0dTPVFk
/Pjj5tl+vatzoiLDmnSpbETjID+dG7IBYxOtVlH61ZEMV2jUHnGPcZfv5UFMQD6m
hqV1VSzTO+X4EhT1fVIRzvM3A21YbKof3z9HxxldizhjV7kAihBrD/2i1EDOAZwF
tufQVG8Dy5+pUl21xcBfcmAJ8Bqz5Dk6jwClRUMSGv+WNb+RG7an1UkszVnPGv0G
D65UnVDtAgMBAAECggEABIUSNrgobj7rs0K2e0TvRdcq20iL3F/LTig7BDtKANht
YZFUYGbIsLk0YGas1gPHciIBtxNGx8Ofw5kkiZIE3zXtjkdOfe62CPT59lgAPjeh
PdpIk3YjzX8bFkTz6hlXAkpkBKvnyIkUeZ3gxR9EG78re1wZ3Il0ckQ0O7a8/ozZ
D4OSnm57Ya9vSDboph+kfa9zH30dPKbkjyaJOsFGgn0/wgQ/9omir3Ty0nqMPD3r
vgertcek5GXXXd0HxZ2nDPX5K3v3eKNfq5ivdDzEi2rH4JHrEjIUaUehqNhl+ah8
v5u9lfaRsN0AvV7Cx3ZoV2sh9s/F0vO9N42bhAQteQKBgQD4ldOFXHi1yQAnyNbT
MsvU3UAdffbzKg7u3NxtBInFLpXbgkTDkCzQK1flyJeYMBSQQxniPhNKVVMjJaOa
FBlPFssf0uV3VHHj9bJsegnHC1VUaE6mUMIlZ9L1En1An21q+fdGdf58OK0ozWzP
SeK42pAGZul/hXGKkTl1NXk8OQKBgQDjb+zdSUJsZfmunYHZ6BQhDBpTFy0a4f0q
LcGf4e4LWzYArN2X3buKhPRNn8j7g6ThU9c2HErAYAVbfRdMRjW+372EWf+v7Skq
KKq3kf2WbFn9UDo6yNs3dQOWM8yX7w+1LsvkbDqUYMOpYdwj0Ts5Sj7Xa82vrnmm
gdloMcLiVQKBgGcE8JwcLopnuoDCYwUzz/Vvm9qdEXLEw1uKfG9Rqibln4VQ/15s
qaW70LmR4AFaK6t9o4R3ZVcw9TtjpeF84uA7+gI+TIqfnHN0p3T9PoAW2k4YzD9w
yITn+i1GdFILwDTNUwUIcWbZtilquOVPIX6qVXXRwILwspVSihVhC9VBAoGBAL4Z
fUfwxd5I1gtHh5OVUc3VieNNidvgbHTmzeJPO2KumFK0KnuXT/wV6QVAuwLPI+9a
3pVRzIFDQPcZdXHBdYbXCFcpyndHqZKdbyQEmIs0gXsjpagg3mpaGedf4HBV1zE/
rhh9BfGjd6eKLjCZ4ACguCni54ciNHgiLI/Ul+oJAoGAQMprKI4uPfAnvVJuEN20
P8prsJpcudsgdDNazodKEWo6uhuqcpHF6aECgulSfrn6ewmt4aiLrzmiWXlC2z1z
EXDKSdk+jA/7RJL+oLN4WYBI9qtA5tAdYbfcoJmZgB/jXdqkPe3aD/mdqn7NfxmG
zIjGgAIFvv2yb0Oph3pGMXo=
-----END PRIVATE KEY-----

21
certificates/snakeoil.pem Normal file
View file

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIUcywFzfoAiMaUnAfJah7Yy3pq4rAwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yNTA3MTAxOTI5MjRaGA85OTk5
MTIzMTE5MjkyNFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBANzZiYb3S+Yb86DWGm5+7zViMNo80Y52wNyNqADS
YUWzaQYBOuD3klk1y7kB5/yuikQhX2a7f77M/wpWM8P7scnZ4KRPW1PggDL2rglA
AS/V5/KMdtx+BcCE9GGKXaFIgJLaUKcvR1M9UWT8+OPm2X69q3OiIsOadKlsROMg
P50bsgFjE61WUfrVkQxXaNQecY9xl+/lQUxAPqaGpXVVLNM75fgSFPV9UhHO8zcD
bVhsqh/fP0fHGV2LOGNXuQCKEGsP/aLUQM4BnAW259BUbwPLn6lSXbXFwF9yYAnw
GrPkOTqPAKVFQxIa/5Y1v5EbtqfVSSzNWc8a/QYPrlSdUO0CAwEAAaNTMFEwHQYD
VR0OBBYEFMFWyHV3/5i/Ue5HsCST/wLRPeOEMB8GA1UdIwQYMBaAFMFWyHV3/5i/
Ue5HsCST/wLRPeOEMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
AJ2sUw5r3JL8LnczTaiiyn36vuCh0eOlzKDadTH9Wk5QGYShKx8a8EcePJ4+l7yL
0f5S9npxXCSRF4daG0BZNBQSTpGu/sT6hJ5/Knmpso8w2PWFPMaYhNi+6zQJ3Bno
sfsJgoVsklUWMRuCVcoyLpAbc0UEb+uuDuLeQTdYP9PzmDp8/UUZBvJ/NznGrFZt
aKnx/g6Qu0gdQKH5pGL3C707CxsvM54UXi5pswbs0kqql9oVTKsAukPeGMXl2num
dSZy+yNAGCsuUGJhG0yatZh473nYYtV4fzzvPIorCrjjvada6Z8EM3RQBOmDT9Oc
RRed5U4jJ3IRbYBF1Q8tagg=
-----END CERTIFICATE-----

17
docker-compose.yml Normal file
View file

@ -0,0 +1,17 @@
services:
webserver:
build:
context: .
dockerfile: Containerfile
environment:
DOMAIN: example.localhost
SERVER_ID: development
ACME_CHALLENGE_URL: http://localhost/acme-challenge
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./run/html:/var/www/html
- ./run/certs:/etc/ssl/certs
- ./run/reload:/var/run/nginx-reload

View file

@ -0,0 +1,17 @@
#!/bin/sh
set -euo pipefail
ls -la /etc/ssl
ls -la /etc/nginx
CERTIFICATE_ROOT="/etc/ssl"
CERTIFICATE_DOMAIN_ROOT="$CERTIFICATE_ROOT/certs/$DOMAIN"
mkdir -p "$CERTIFICATE_DOMAIN_ROOT"
cp -n "$CERTIFICATE_ROOT/snakeoil.pem" "$CERTIFICATE_DOMAIN_ROOT/fullchain.pem"
cp -n "$CERTIFICATE_ROOT/snakeoil.key" "$CERTIFICATE_DOMAIN_ROOT/privkey.pem"
chmod 700 "$CERTIFICATE_DOMAIN_ROOT/fullchain.pem"
chmod 700 "$CERTIFICATE_DOMAIN_ROOT/privkey.pem"

View file

@ -0,0 +1,7 @@
#!/bin/sh
set -euo pipefail
echo "Starting reload watcher..."
/opt/scripts/watch-reload.sh &

90
nginx/nginx.conf Normal file
View file

@ -0,0 +1,90 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_user [$time_local] "$request" '
'$status $body_bytes_sent bytes "$http_referer" '
'"$http_x_forwarded_for"';
# While I removed PII from the above log format, still better not logging
access_log /dev/null main; # /var/log/nginx/access.log main;
server_tokens off;
sendfile on;
tcp_nopush on;
quic_retry on;
quic_gso on;
ssl_early_data on; # READ https://blog.cloudflare.com/introducing-0-rtt/#whats-the-catch
keepalive_timeout 65;
gzip on;
gzip_types *;
gzip_min_length 1000;
gzip_proxied any;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400';
add_header Strict-Transport-Security "max-age=63072000; preload" always;
# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519:prime256v1:secp384r1;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1h;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
# Make sure to generate it first
ssl_dhparam /etc/ssl/dhparam.pem;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# async 'resolver' is important for proper operation of OCSP stapling
resolver [2001:4860:4860::8888] 8.8.8.8;
# If certificates are marked OCSP Must-Staple, consider managing the
# OCSP stapling cache with an external script, e.g. certbot-ocsp-fetcher
# HTTPS redirect / HSTS
server {
listen 80 default_server;
listen [::]:80 default_server;
return 301 https://$host$request_uri;
}
# default HTTPS server
server {
listen 443 ssl default_server;
listen 443 quic reuseport default_server;
listen [::]:443 ssl default_server;
ssl_certificate /etc/ssl/snakeoil.pem;
ssl_certificate_key /etc/ssl/snakeoil.key;
server_name _;
return 444;
}
include /etc/nginx/conf.d/*.conf;
}

View file

@ -0,0 +1,18 @@
server {
listen 443 ssl;
listen 443 quic;
listen [::]:443 ssl;
listen [::]:443 quic;
server_name ${DOMAIN};
ssl_certificate /etc/ssl/certs/${DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/ssl/certs/${DOMAIN}/privkey.pem;
root /var/www/html/${DOMAIN};
index index.html;
location .well-known/acme-challenge {
proxy_pass ${ACME_CHALLENGE_URL};
}
}

5
scripts/reload.sh Normal file
View file

@ -0,0 +1,5 @@
#!/bin/sh
set -euo pipefail
nginx -s reload

15
scripts/watch-reload.sh Normal file
View file

@ -0,0 +1,15 @@
#!/bin/sh
set -euo pipefail
touch "$RELOAD_FILE"
# The loop will run once every time the file is saved.
while inotifywait -e close_write "$RELOAD_FILE"; do
echo "File '$RELOAD_FILE' changed. Reloading."
if nginx -t; then
nginx -s reload
else
echo "Nginx configuration is invalid. Skipping reload."
fi
done