feat: init

This commit is contained in:
max_richter 2025-05-12 13:09:43 +02:00
commit 516eb78832
Signed by: max
GPG Key ID: 51973802EF3F77C5
12 changed files with 403 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/compose.override.yml
/config/traefik/dynamic/*
!/config/traefik/dynamic/ssl.yaml
!/config/traefik/dynamic/dashboard.yaml

104
README.md Normal file
View File

@ -0,0 +1,104 @@
# Local HTTPS with Traefik and Step-CA
This setup enables automatic HTTPS and local domain routing for Docker Compose services. Traefik uses Step-CA to issue certificates and route .dev.local domains securely with minimal configuration.
## Setup
1. Clone this repository and navigate into the directory.
2. Start the services:
```bash
docker compose up -d
```
3. Trust the Step-CA root certificate:
```bash
curl -k https://localhost:9000/roots.pem -o roots.pem
sudo trust anchor --store roots.pem
rm roots.pem
```
## How to Use
1. Add a label to your docker compose service:
```yaml
labels:
serviceName: my-app
```
2. Your service will be accessible at `https://my-app.dev.local`.
## Troubleshooting
If certificates are not renewed or have expired
```bash
docker compose up -d --force-recreate traefik
```
## Tipps
### Custom Domain Suffix
For example `dev.cool` 😎
Replace `.dev.local` with your custom domain suffix in the `config/traefik/traefik.yml` file:
```yaml
...
docker:
defaultRule: |
Host(`{{ trim (index .Labels "serviceName") }}.dev.cool`) {{range $i, $domain := splitList "," (index .Labels "serviceDomains")}}{{if ne $domain ""}}|| Host(`{{$domain}}`){{end}}{{end}}
...
```
Replace `.dev.local` with your custom domain suffix in the `config/dns/config.sample.json` file:
```json
...
{
"id": 2,
"hostname": ".dev.cool",
"ip": "",
"target": "host.docker",
"ttl": 3600,
"type": "CNAME"
}
...
```
Remove the dns_config volume
```bash
docker compose down
docker compose volukme rm dns_config
docker compose up -d
```
### Certificate Lifetime
To ensure Traefik has enough time to renew certificates, increase their duration:
```bash
docker compose exec step step ca provisioner update acme \
--x509-min-dur=20m \
--x509-max-dur=8760h \
--x509-default-dur=2160h
```
### Use the preconfigured services
If you use the preconfigured services, you can add the following snippet to you `.bashrc/.zshrc` to easily start, stop, and manage the services.
```bash
dev () {
PROJECT_DIR="$HOME/Projects/dev/services"
case "$1" in
(start) shift
docker compose -f "$PROJECT_DIR/docker-compose.yml" --profile "$@" up -d ;;
(restart) shift
docker compose -f "$PROJECT_DIR/docker-compose.yml" --profile "$@" restart ;;
(stop) shift
docker compose -f "$PROJECT_DIR/docker-compose.yml" --profile "$@" down --remove-orphans ;;
(logs) shift
docker compose -f "$PROJECT_DIR/docker-compose.yml" --profile "$@" logs -f ;;
(*) echo "Usage: dev {start|restart|stop|logs} [services...]" ;;
esac
}
```

84
compose.yml Normal file
View File

@ -0,0 +1,84 @@
services:
dns:
image: defreitas/dns-proxy-server:3.32.4
restart: unless-stopped
entrypoint: /conf/entrypoint.sh
environment:
MG_LOG_LEVEL: info
MG_DOMAIN: docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./config/dns:/conf
- dns_config:/app/conf
labels:
serviceName: dps
expose:
- "5380"
networks:
default:
ipv4_address: 172.157.5.249
traefik:
image: traefik:3.3
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./config/traefik:/etc/traefik
- traefik:/traefik
- step:/step:ro
network_mode: host
environment:
LEGO_CA_CERTIFICATES: /step/certs/root_ca.crt
LEGO_CA_SERVERNAME: localhost
depends_on:
step:
condition: service_healthy
restart: false
step:
image: smallstep/step-ca:latest
working_dir: /home/step
restart: unless-stopped
volumes:
- step:/home/step
environment:
DOCKER_STEPCA_INIT_NAME: Max authority
DOCKER_STEPCA_INIT_DNS_NAMES: localhost,step.dev.local
DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT: "false"
DOCKER_STEPCA_INIT_ACME: "true"
labels:
serviceName: step
traefik.tcp.routers.step.rule: HostSNI(`step.dev.local`)
traefik.tcp.routers.step.tls.passthrough: "true"
ports:
- "9000:9000"
command: step-ca --resolver "172.157.5.249:53" --password-file "/home/step/secrets/password" "/home/step/config/ca.json"
healthcheck:
test: ["CMD", "step", "ca", "health"]
interval: 60s
start_period: 10s
start_interval: 1s
dns:
- 172.157.5.249
depends_on:
dns:
condition: service_started
restart: false
volumes:
dns_config: ~
traefik: ~
step: ~
networks:
default:
name: dps
driver: bridge
ipam:
driver: default
config:
- subnet: 172.157.0.0/16
ip_range: 172.157.5.0/24
gateway: 172.157.5.1
- subnet: fc00:5c6f:db50::/64
gateway: fc00:5c6f:db50::1

View File

@ -0,0 +1,41 @@
{
"version": 2,
"activeEnv": "",
"webServerPort": null,
"dnsServerPort": null,
"defaultDns": null,
"logLevel": null,
"logFile": null,
"registerContainerNames": null,
"hostMachineHostname": null,
"domain": "docker",
"dpsNetwork": true,
"dpsNetworkAutoConnect": true,
"resolvConfOverrideNameServers": false,
"noRemoteServers": true,
"noEntriesResponseCode": 2,
"remoteDnsServers": [],
"envs": [
{
"name": "",
"hostnames": [
{
"id": 1,
"hostname": ".vm",
"ip": "",
"target": "host.docker",
"ttl": 3600,
"type": "CNAME"
},
{
"id": 2,
"hostname": ".dev.local",
"ip": "",
"target": "host.docker",
"ttl": 3600,
"type": "CNAME"
}
]
}
]
},

25
config/dns/entrypoint.sh Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
if [ ! -d "/app/conf" ]; then
mkdir /app/conf
fi
if [ ! -f "/app/conf/config.json" ]; then
cp /conf/config.sample.json /app/conf/config.json
elif [ "/conf/config.sample.json" -nt "/app/conf/config.json" ]; then
echo "example config file is newer than existing config."
echo "replacing existing file by new config"
CONFIG_BACKUP="/app/conf/config_$(date +"%Y%m%d_%H%M%s").json"
echo "saving existing config as ${CONFIG_BACKUP}"
cp /app/conf/config.json ${CONFIG_BACKUP}
cp /conf/config.sample.json /app/conf/config.json
fi
if [ -z "$@" ] ; then
exec "/app/dns-proxy-server" -XX:MaxHeapSize=50m -XX:MaxNewSize=10m
else
exec "$@"
fi

View File

@ -0,0 +1,5 @@
http:
routers:
api:
service: api@internal
rule: "Host(`traefik.vm`)"

View File

@ -0,0 +1,16 @@
http:
middlewares:
redirect-https:
redirectScheme:
scheme: https
permanent: false
routers:
https-router:
rule: "!Host(`localhost`)"
priority: 999999
middlewares:
- redirect-https
service: noop@internal
entrypoints:
- http

View File

@ -0,0 +1,37 @@
log:
level: INFO
api:
dashboard: true
disableDashboardAd: true
entryPoints:
http:
address: :80
https:
address: :443
asDefault: true
http:
tls:
certResolver: step
providers:
file:
directory: /etc/traefik/dynamic
watch: true
docker:
defaultRule: |
Host(`{{ trim (index .Labels "serviceName") }}.dev.local`) {{range $i, $domain := splitList "," (index .Labels "serviceDomains")}}{{if ne $domain ""}}|| Host(`{{$domain}}`){{end}}{{end}}
constraints: LabelRegex(`serviceName`, `.+`) && !Label(`com.docker.compose.oneoff`, `True`)
certificatesResolvers:
step:
acme:
caServer: https://localhost:9000/acme/acme/directory
certificatesDuration: 24
email: dev@example.com
storage: /traefik/certs.json
httpChallenge:
entryPoint: http

0
services/.gitignore vendored Normal file
View File

View File

@ -0,0 +1,67 @@
services:
mailpit:
profiles:
- mail
image: axllent/mailpit
ports:
- 1025:1025
environment:
MP_UI_BIND_ADDR: "0.0.0.0:8085"
labels:
serviceName: mail
traefik.http.services.mail.loadbalancer.server.port: 8085
datasette:
ports:
- 8001:8001
volumes:
- datasette:/mnt
image: datasetteproject/datasette
command: datasette -p 8001 -h 0.0.0.0 --plugins-dir=/mnt/plugins/ --config default_page_size:500 /mnt/data/qs-monitor-usage.db
profiles:
- data
labels:
serviceName: data
traefik.http.services.data.loadbalancer.server.port: 8001
silverbullet:
profiles:
- notes
image: ghcr.io/silverbulletmd/silverbullet:v2
volumes:
- ~/Notes:/space
labels:
serviceName: notes
db:
profiles:
- db
build: docker
image: dbgate/dbgate:beta-alpine
labels:
serviceName: db
volumes:
- dbgate:/root/.dbgate
grafana:
profiles:
- logs
image: grafana/grafana:11.2.0
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_BASIC_ENABLED=false
- GF_FEATURE_TOGGLES_ENABLE=accessControlOnCall lokiLogsDataplane exploreLogsShardSplitting
- GF_PLUGINS_PREINSTALL_DISABLED=true
- GF_INSTALL_PLUGINS=https://storage.googleapis.com/integration-artifacts/grafana-lokiexplore-app/grafana-lokiexplore-app-latest.zip;grafana-lokiexplore-app
labels:
serviceName: grafana
volumes:
- ./provisioning/grafana:/etc/grafana/provisioning
extra_hosts:
- 'host.docker.internal:host-gateway'
volumes:
dbgate: ~
datasette: ~

View File

@ -0,0 +1,8 @@
apiVersion: 1
datasources:
- name: gdev-loki
type: loki
uid: gdev-loki
access: proxy
url: http://host.docker.internal:3100

View File

@ -0,0 +1,12 @@
apiVersion: 1
apps:
- type: "grafana-lokiexplore-app"
org_id: 1
org_name: "Grafana"
disabled: false
jsonData:
apiUrl: http://default-url.com
isApiKeySet: true
secureJsonData:
apiKey: secret-key