Skip to content

Commit 4b8a2e7

Browse files
committed
feat: make dev.up wait until services are actually running
All too often I'll run dev.up and get all green, but eventually discover that half the services did not actually start for one reason or another. The main issue isn't having to fix them, it's not knowing which ones failed and/or not knowing that any failed at all. Terrible UX. This change makes it so that dev.up will not only report when each service has *started*, but also wait for them to become *healthy*, and also clearly state when a service fails to become healthy (e.g. missing imports). This PR also just does a lot to make everything a bit more normalized and DRY across the board.
1 parent 4487f94 commit 4b8a2e7

4 files changed

Lines changed: 287 additions & 139 deletions

File tree

‎Makefile‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ dev.up.with-watchers.%: ## Bring up services and their dependencies + asset watc
254254
dev.up.without-deps: _expects-service-list.dev.up.without-deps
255255

256256
dev.up.without-deps.%: dev.check-memory ## Bring up services by themselves.
257-
docker compose up -d --no-deps $$(echo $* | tr + " ")
257+
docker compose up -d --wait --no-deps $$(echo $* | tr + " ")
258258

259259
dev.up.without-deps.shell: _expects-service.dev.up.without-deps.shell
260260

@@ -269,7 +269,7 @@ dev.up.large-and-slow: dev.up.$(DEFAULT_SERVICES) ## Bring up default services.
269269
@echo # at least one statement so that dev.up.% doesn't run too
270270

271271
dev.up.%: dev.check-memory ## Bring up services and their dependencies.
272-
docker compose up -d $$(echo $* | tr + " ")
272+
docker compose up -d --wait $$(echo $* | tr + " ")
273273
ifeq ($(ALWAYS_CACHE_PROGRAMS),true)
274274
make dev.cache-programs
275275
endif

‎common.yml‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Abstract base services, inherited by concrete services via `extends`.
2+
services:
3+
4+
# Traffic-serving backend app (Django/IDA) services.
5+
backend-app:
6+
stdin_open: true # Allows `make dev.attach.<service>` to work correctly.
7+
tty: true
8+
environment:
9+
# This default works well for the vast majority of django services.
10+
DEVSERVER_CHECK: "python manage.py check"
11+
# Concrete services may set these extra environment variables:
12+
#
13+
# - DEVSERVER_PRECHECK allows some services to run extra commands at the very beginning.
14+
# This is rarely needed.
15+
# - DEVSERVER_CHECK runs once and exits quickly on failure. This allows docker-compose to give
16+
# near-instant feedback to users when a service cannot start (e.g. due to missing imports).
17+
# - DEVSERVER_CMD runs inside an infinite loop to allow `make dev.restart-devserver.<service>`
18+
# to work correctly.
19+
#
20+
# Examples:
21+
#
22+
# environment:
23+
# DEVSERVER_PRECHECK: "source /edx/app/foo/foo_env" # optional
24+
# DEVSERVER_CHECK: "python manage.py check" # optional
25+
# DEVSERVER_CMD: "python manage.py runserver 0.0.0.0:18000"
26+
command:
27+
- bash
28+
- -c
29+
- |
30+
if [ -n "$$DEVSERVER_PRECHECK" ]; then eval "$$DEVSERVER_PRECHECK"; fi
31+
if [ -n "$$DEVSERVER_CHECK" ]; then
32+
eval "$$DEVSERVER_CHECK" || { echo "pre-flight check failed; giving up." >&2; exit 1; }
33+
fi
34+
while true; do
35+
eval "$$DEVSERVER_CMD"
36+
sleep 2
37+
done
38+
# Configure a healthcheck to allow `make dev.up.<servce>` to give accurate feedback about
39+
# whether the service actually started successfully and is serving traffic.
40+
# Set HEALTHCHECK_TARGET to the full URL curl should hit, e.g. "http://localhost:18000/heartbeat"
41+
healthcheck:
42+
test: ["CMD-SHELL", "curl -fsS $$HEALTHCHECK_TARGET || exit 1"]
43+
interval: 10s
44+
timeout: 10s
45+
retries: 20
46+
start_period: 240s
47+
start_interval: 5s # poll fast during grace period so a healthy boot flips status quickly
48+
49+
# All microfrontends.
50+
microfrontend:
51+
# Use `npm ci` rather than `npm install` for a few reasons:
52+
#
53+
# - Repeatability: Respect the currently checked out package
54+
# versions rather than upgrading when package.json and
55+
# package-lock.json don't match. (Two people using this at
56+
# different times on the same commit should get the same
57+
# results.)
58+
# - Immutability: Don't change the repo's working directory
59+
# unexpectedly when there's a lock mismatch.
60+
#
61+
# Fail fast if package install fails to avoid mysterious
62+
# errors later.
63+
command:
64+
- bash
65+
- -c
66+
- |
67+
npm ci || exit 1
68+
if [ -n "$${PARAGON_BRAND_PACKAGE}" ]; then
69+
npx paragon install-theme "$${PARAGON_BRAND_PACKAGE}" || exit 1
70+
fi
71+
while true; do
72+
npm start
73+
sleep 2
74+
done
75+
stdin_open: true
76+
tty: true
77+
image: node:18
78+
environment:
79+
- NODE_ENV=development
80+
# More generous than backend-app because a cold `npm ci` can take
81+
# several minutes.
82+
healthcheck:
83+
test: ["CMD-SHELL", "curl -fsS $$HEALTHCHECK_TARGET || exit 1"]
84+
interval: 10s
85+
timeout: 10s
86+
retries: 30
87+
start_period: 300s

0 commit comments

Comments
 (0)