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