Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
129 lines (111 loc) · 5.59 KB
/
Copy pathdocker-compose.yml
File metadata and controls
129 lines (111 loc) · 5.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# Failover LB. One of these runs on every nginx box you have.
#
# There is no central server to lose. Every node runs the same container and
# they form a trust group. One node is ACTIVE and takes the writes, the rest
# sit as STANDBY and stay in sync. Losing any one node, even the active one,
# does not take your nginx config or your certs with it.
#
# cp .env.example .env # then edit .env
# docker compose up -d
#
# Set up the first node and join the rest with ./install.sh. See the README.
services:
nginx-fleet-manager:
build:
context: .
dockerfile: Dockerfile
image: nginx-fleet-manager:${NFM_VERSION:-1.0.0}
container_name: nginx-fleet-manager
restart: unless-stopped
# Host networking, and yes we mean it. Three reasons, all of them real:
#
# 1. Health checks have to reach your backends the same way nginx does,
# from the same source address. On a bridge network the NAT would
# make our idea of a pool look different from what nginx sees, and
# then you are debugging a difference that does not exist.
# 2. Finding the public IP has to watch the host's real path out to the
# internet. That answer is what picks which node asks for the cert.
# 3. Peers talk to each other on the host's real addresses.
network_mode: host
environment:
# ---- who this node is ---------------------------------------------
NFM_NODE_NAME: ${NFM_NODE_NAME:?set NFM_NODE_NAME in .env}
NFM_NODE_ADDRESS: ${NFM_NODE_ADDRESS:?set NFM_NODE_ADDRESS in .env}
NFM_CLUSTER_PRIORITY: ${NFM_CLUSTER_PRIORITY:-100}
# ---- ports we listen on -------------------------------------------
NFM_GUI_PORT: ${NFM_GUI_PORT:-7443}
NFM_PEER_PORT: ${NFM_PEER_PORT:-7444}
NFM_BIND_ADDRESS: ${NFM_BIND_ADDRESS:-0.0.0.0}
# ---- security ------------------------------------------------------
# install.sh generates this. It encrypts the OIDC client secret, any DNS
# provider tokens, and the peer keys where they sit in the database.
NFM_SECRET_KEY: ${NFM_SECRET_KEY:?set NFM_SECRET_KEY in .env}
# Which networks are even allowed to load the web GUI. Leaving it empty
# means anybody can, so set it before this sees anything but a jump box.
NFM_ADMIN_ALLOWLIST: ${NFM_ADMIN_ALLOWLIST:-}
# Whose X-Forwarded-For we believe. Empty means we ignore the header and
# use the address the connection came from, which is right when the GUI is
# reached directly. Set it to the proxy's address, and only that, when
# you put one in front.
NFM_TRUSTED_PROXIES: ${NFM_TRUSTED_PROXIES:-}
# Serving the GUI under a path, for putting it behind a reverse proxy at
# something like /lb. Every link, form action and cookie path is built
# from this. It was missing here, so the setting existed, the templates
# used it, and there was no way to give it a value: the login page came
# out with no stylesheet, which is exactly the symptom its own comment in
# config.py warns about.
NFM_ROOT_PATH: ${NFM_ROOT_PATH:-}
NFM_SESSION_TIMEOUT_MINUTES: ${NFM_SESSION_TIMEOUT_MINUTES:-60}
# ---- OIDC, optional. Local break glass logins always work. --------
NFM_OIDC_ENABLED: ${NFM_OIDC_ENABLED:-false}
NFM_OIDC_DISCOVERY_URL: ${NFM_OIDC_DISCOVERY_URL:-}
NFM_OIDC_CLIENT_ID: ${NFM_OIDC_CLIENT_ID:-}
NFM_OIDC_CLIENT_SECRET: ${NFM_OIDC_CLIENT_SECRET:-}
NFM_OIDC_REDIRECT_BASE: ${NFM_OIDC_REDIRECT_BASE:-}
NFM_OIDC_GROUPS_CLAIM: ${NFM_OIDC_GROUPS_CLAIM:-groups}
NFM_OIDC_ADMIN_GROUP: ${NFM_OIDC_ADMIN_GROUP:-nginx-admins}
NFM_OIDC_OPERATOR_GROUP: ${NFM_OIDC_OPERATOR_GROUP:-nginx-operators}
NFM_OIDC_VIEWER_GROUP: ${NFM_OIDC_VIEWER_GROUP:-nginx-viewers}
# ---- certificates ---------------------------------------------------
NFM_ACME_EMAIL: ${NFM_ACME_EMAIL:-}
NFM_ACME_STAGING: ${NFM_ACME_STAGING:-false}
# ---- day to day -----------------------------------------------------
NFM_LOG_LEVEL: ${NFM_LOG_LEVEL:-info}
NFM_AGENT_SOCKET: /run/nginxmgr/agent.sock
NFM_DATA_DIR: /data
volumes:
# This socket is the only way we reach the host. Everything that needs
# root goes through it: writing nginx config, reloading the service,
# running certbot, reading cert files. The far side checks every request
# against a short verb list and a path allow list. The container has no
# write access to /etc/nginx or /etc/letsencrypt on its own.
- /run/nginxmgr:/run/nginxmgr
# Database, cluster CA, and this node's key pair. Back this up.
- nfm-data:/data
# Read only look at the nginx config so the GUI can show you what is
# really running, compare it to what we think we wrote, and tell you when
# somebody edited a file by hand. We never write through this mount.
- /etc/nginx:/host/etc/nginx:ro
- /etc/letsencrypt:/host/etc/letsencrypt:ro
- /etc/localtime:/etc/localtime:ro
# Nothing in here needs extra powers. We get to the agent socket by being
# in the right group, not by holding capabilities.
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,size=64m
# This matches the nginxmgr group install.sh makes on the host and puts on
# the agent socket. Without it a non root uid in here cannot open it.
group_add:
- ${NFM_HOST_GID:-10001}
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"
volumes:
nfm-data:
driver: local