feat: init
This commit is contained in:
commit
516eb78832
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal 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
104
README.md
Normal 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
84
compose.yml
Normal 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
|
41
config/dns/config.sample.json
Normal file
41
config/dns/config.sample.json
Normal 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
25
config/dns/entrypoint.sh
Executable 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
|
5
config/traefik/dynamic/dashboard.yaml
Normal file
5
config/traefik/dynamic/dashboard.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
http:
|
||||||
|
routers:
|
||||||
|
api:
|
||||||
|
service: api@internal
|
||||||
|
rule: "Host(`traefik.vm`)"
|
16
config/traefik/dynamic/ssl.yaml
Normal file
16
config/traefik/dynamic/ssl.yaml
Normal 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
|
37
config/traefik/traefik.yml
Normal file
37
config/traefik/traefik.yml
Normal 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
0
services/.gitignore
vendored
Normal file
67
services/docker-compose.yml
Normal file
67
services/docker-compose.yml
Normal 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: ~
|
8
services/provisioning/grafana/datasources/default.yaml
Normal file
8
services/provisioning/grafana/datasources/default.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
datasources:
|
||||||
|
- name: gdev-loki
|
||||||
|
type: loki
|
||||||
|
uid: gdev-loki
|
||||||
|
access: proxy
|
||||||
|
url: http://host.docker.internal:3100
|
12
services/provisioning/grafana/plugins/app.yaml
Normal file
12
services/provisioning/grafana/plugins/app.yaml
Normal 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
|
Loading…
x
Reference in New Issue
Block a user