Skip to content
Open
48 changes: 48 additions & 0 deletions .env.traefik.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Traefik Configuration
# Copy this file to .env.traefik and configure according to your needs
#
# SERVER_* values are application/setup specific
# TRAEFIK_* values are Traefik configuration values,
# @see https://doc.traefik.io/traefik/reference/install-configuration/boot-environment/#environment-variables

# If you use an external proxy you can configure the docker network name here
SERVER_FRONTEND_NETWORK=frontend

# Domain for Traefik dashboard access
# Example: traefik.example.com
SERVER_DOMAIN=traefik.example.com

# Dashboard Basic Authentication
# Generate with: htpasswd -n admin
# Or use: echo $(htpasswd -n admin) | sed -e s/\\$/\\$\\$/g
# Example: admin:$$apr1$$xyz123...
SERVER_DASHBOARD_AUTH=admin:password


######### HTTPS/SSL setup #########
# This setup can use either Let's Encrypt (default) or local cert/key fiels. This is controlled by setting
# SERVER_CERT_PROVIDER to either "letsencrypt" or "cert-file"

##### Let's Encrypt #####
# By default this setup uses Let's Encrypt for https certificates. Alternatively you can provide a local certificate.
SERVER_CERT_PROVIDER=letsencrypt

# Email for Let's Encrypt notifications
# Example: [email protected]
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL=admin@example.com

# ACME (Let's Encrypt) CA Server
# For production (default): https://acme-v02.api.letsencrypt.org/directory
# For staging/testing: https://acme-staging-v02.api.letsencrypt.org/directory
# Staging is recommended for testing to avoid hitting rate limits
# TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_CASERVER=https://acme-staging-v02.api.letsencrypt.org/directory
# For production
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_CASERVER=https://acme-v02.api.letsencrypt.org/directory


##### Local/custom certificae #####
# SERVER_CERT_PROVIDER=cert-file
# Palce a valid SSL certificate and private key for the domain in the traefik/ssl` directory.
# Then set the following to match the filenames for the provided files.
# SERVER_CUSTOM_CERT_FILE=docker.crt
# SERVER_CUSTOM_KEY_FILE=docker.key
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.env.local
.env.docker.local
.env.traefik
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,18 @@ sudo passwd deploy
sudo usermod -aG docker deploy
```

## Secure Mode Requirement
## Traefik Configuration

This project can only run in secure mode using HTTPS (port 443). To ensure proper functionality, you must provide a valid domain name and an SSL certificate.
### Secure Mode Requirement

This project can only run in secure mode using HTTPS (port 443). A Traefik reverse proxy will handle HTTPS using either
* Let's encrypt certificates (default)
* Custom certificate/key files

### Steps to Configure Secure Mode:
1. **Domain Name**: Use a fully qualified domain name (FQDN) that resolves to your server's IP address.
2. **SSL Certificate**: Provide a valid SSL certificate and private key for the domain.
2. **SSL Certificate**, either:
- Let traefik generate a certificate using Let's Encrypt (default).
- Place the certificate file (`docker.crt`) and the private key file (`docker.key`) in the `traefik/ssl` directory.
3. **Update Configuration**: Ensure the domain name is correctly configured in the `.env.docker.local` file.

Expand All @@ -60,6 +65,7 @@ Without a valid domain name and SSL certificate, the project will not function a
The project uses a `Taskfile.yml` to simplify common operations. Below is a list of the most important tasks you can run:

### Installation and Setup
- **`task traefik_env`**: Configures Traefik to use Let's Encrypt certificates or custom certificates.
- **`task install`**: Installs the project, pulls Docker images, sets up the database, and initializes the environment.
- **`task reinstall`**: Reinstalls the project from scratch, removing all containers, volumes, and the database.
- **`task up`**: Starts the environment without altering the existing state of the containers.
Expand Down
55 changes: 55 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,56 @@ tasks:
- task load_templates
- task _show_notes

traefik_env:
desc: Setup .env.traefik (domain, email, dashboard auth)
cmds:
- |
if [ ! -f .env.traefik ]; then
echo ".env.traefik does not exist. Copying .env.traefik.example to .env.traefik..."
cp .env.traefik.example .env.traefik
fi

# Ensure htpasswd is installed
if ! command -v htpasswd >/dev/null 2>&1; then
echo "Error: 'htpasswd' command not found."
echo "Please install it (usually from the 'apache2-utils' or 'httpd-tools' package) and try again."
exit 1
fi

echo ""

echo "Configure Traefik environment"
echo "===================================================="
printf "Enter server domain (e.g. example.com): "
read SERVER_DOMAIN
printf "Enter email for Let's Encrypt (e.g. [email protected]): "
read TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL
printf "Enter Traefik dashboard/admin username: "
read SERVER_DASHBOARD_USERNAME
printf "Enter Traefik dashboard/admin password: "
read -s SERVER_DASHBOARD_PASSWORD

# Generate htpasswd entry (username:hash)
HTPASSWD_RAW=$(htpasswd -nb "${SERVER_DASHBOARD_USERNAME}" "${SERVER_DASHBOARD_PASSWORD}")

# Escape characters that break sed/env ($, /, &)
HTPASSWD_ESCAPED=$(printf '%s\n' "$HTPASSWD_RAW" \
| sed -e 's/[\/&]/\\&/g' -e 's/\$/\\$/g')

# Update variables in .env.traefik
sed -i "s/^SERVER_DOMAIN=.*/SERVER_DOMAIN=${SERVER_DOMAIN}/" .env.traefik
sed -i "s/^TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL=.*/TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL=${TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL}/" .env.traefik
sed -i "s/^SERVER_DASHBOARD_AUTH=.*/SERVER_DASHBOARD_AUTH=${HTPASSWD_ESCAPED}/" .env.traefik

echo "===================================================="
echo ".env.traefik has been updated."
echo "Domain: ${SERVER_DOMAIN}"
echo "Email: ${TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL}"
echo "User: ${SERVER_DASHBOARD_USERNAME}"
echo "Password: (hidden)"
echo "Auth: (htpasswd line stored in SERVER_DASHBOARD_AUTH)"
echo "===================================================="

reinstall:
desc: Reinstall from scratch. Removes the database, all containers, and volumes.
deps:
Expand Down Expand Up @@ -208,6 +258,11 @@ tasks:
echo ".env.docker.local does not exist. Copying .env.docker.example to .env.docker.local..."
cp .env.docker.example .env.docker.local
fi
- |
if [ ! -f .env.traefik ]; then
echo ".env.traefik does not exist. Copying .env.traefik.example to .env.traefik..."
cp .env.traefik.example .env.traefik
fi

_dc_compile:
deps:
Expand Down
23 changes: 19 additions & 4 deletions docker-compose.traefik.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
networks:
frontend:
external: true
proxy:
driver: bridge
internal: true

services:
traefik:
image: traefik:v3.2
image: traefik:v3.6
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
env_file:
- path: .env.traefik
required: true
ports:
- "80:80"
- "443:443"
- "8080:8080" # Dashboard
volumes:
- $PWD/traefik/ssl:/certs:ro
- $PWD/traefik/letsencrypt:/letsencrypt
- $PWD/traefik/traefik.yml:/traefik.yml:ro
- $PWD/traefik/dynamic-conf.yaml:/config/dynamic-conf.yaml:ro
- $PWD/traefik/dynamic-conf-${SERVER_CERT_PROVIDER:-letsencrypt}.yaml:/config/dynamic-conf.yaml:ro
networks:
- frontend
- ${SERVER_FRONTEND_NETWORK:-frontend}
- proxy
labels:
- "traefik.enable=true"
# Dashboard authentication
- "traefik.http.middlewares.dashboard-auth.basicauth.users=${SERVER_DASHBOARD_AUTH}"
# Dashboard router
- "traefik.http.routers.dashboard.rule=Host(`${SERVER_DOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=dashboard-auth,security-headers@file"

socket-proxy:
image: itkdev/docker-socket-proxy
Expand Down
44 changes: 44 additions & 0 deletions traefik/dynamic-conf-cert-file.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
http:
middlewares:
security-headers:
headers:
frameDeny: true
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 63072000
customFrameOptionsValue: "SAMEORIGIN"
customResponseHeaders:
X-Robots-Tag: "noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex"
server: ""

https-redirect:
redirectScheme:
scheme: https
permanent: true

tls:
options:
modern:
minVersion: VersionTLS12
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
curvePreferences:
- CurveP521
- CurveP384
sniStrict: true

# Custom certificates (if provided)
# Place your custom certificate files in traefik/ssl/ directory
# Traefik will only use these if the files exist
# If using Let's Encrypt, you can ignore this section or remove the files
certificates:
- certFile: "/certs/{{ env \"SERVER_CUSTOM_CERT_FILE\" }}"
keyFile: "/certs/{{ env \"SERVER_CUSTOM_KEY_FILE\" }}"
36 changes: 36 additions & 0 deletions traefik/dynamic-conf-letsencrypt.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
http:
middlewares:
security-headers:
headers:
frameDeny: true
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 63072000
customFrameOptionsValue: "SAMEORIGIN"
customResponseHeaders:
X-Robots-Tag: "noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex"
server: ""

https-redirect:
redirectScheme:
scheme: https
permanent: true

tls:
options:
modern:
minVersion: VersionTLS12
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
curvePreferences:
- CurveP521
- CurveP384
sniStrict: true
5 changes: 0 additions & 5 deletions traefik/dynamic-conf.yaml

This file was deleted.

4 changes: 4 additions & 0 deletions traefik/letsencrypt/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
37 changes: 33 additions & 4 deletions traefik/traefik.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,56 @@

api:
dashboard: true
insecure: true
debug: true
insecure: false

entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true

websecure:
address: ":443"
http:
tls:
{}
certResolver: letsencrypt
options: modern@file
http3: {}

providers:
file:
directory: /config
watch: true
docker:
endpoint: "tcp://socket-proxy:2375"
exposedByDefault: false

# Let's Encrypt configuration
certificatesResolvers:
letsencrypt:
acme:
# See .env.traefik.example for more info and stg/prod values
# email: <TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL>
# caServer: <TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_CASERVER>
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web

# https://doc.traefik.io/traefik/routing/services/#insecureskipverify
serversTransport:
insecureSkipVerify: true

# Logging
log:
level: INFO
format: json

accessLog:
format: json
fields:
headers:
defaultMode: drop