Initial commit
This commit is contained in:
commit
961e7aebe4
14 changed files with 289 additions and 0 deletions
1
.dockerignore
Normal file
1
.dockerignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
run/
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
run/
|
||||||
30
Containerfile
Normal file
30
Containerfile
Normal 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
26
README.md
Normal 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
13
certificates/dhparam.pem
Normal 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
28
certificates/snakeoil.key
Normal 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
21
certificates/snakeoil.pem
Normal 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
17
docker-compose.yml
Normal 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
|
||||||
17
docker-entrypoint.d/10-initialize-certificates.sh
Executable file
17
docker-entrypoint.d/10-initialize-certificates.sh
Executable 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"
|
||||||
7
docker-entrypoint.d/50-watch-reload.sh
Normal file
7
docker-entrypoint.d/50-watch-reload.sh
Normal 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
90
nginx/nginx.conf
Normal 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;
|
||||||
|
}
|
||||||
18
nginx/templates/default.conf.template
Normal file
18
nginx/templates/default.conf.template
Normal 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
5
scripts/reload.sh
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
nginx -s reload
|
||||||
15
scripts/watch-reload.sh
Normal file
15
scripts/watch-reload.sh
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue