@@ -18,6 +18,12 @@ services:
1818 - ./cryptify/conf/config.dev.toml:/app/config.toml:ro
1919 - ./cryptify/src:/app/src
2020 - ./cryptify/templates:/app/templates
21+ # build.rs feeds `PG_CORE_VERSION` (used by the X-PostGuard
22+ # mail header) from Cargo.lock at compile time. Both files
23+ # need to be visible inside the container, or cargo bails
24+ # on `env!("PG_CORE_VERSION")`.
25+ - ./cryptify/build.rs:/app/build.rs:ro
26+ - ./cryptify/Cargo.lock:/app/Cargo.lock:ro
2127 - cryptify-target:/app/target
2228 environment :
2329 - RUST_LOG=info
@@ -91,6 +97,164 @@ services:
9197 networks :
9298 - postguard
9399
100+ # GlitchTip — open-source, Sentry-compatible error tracker.
101+ # Reports posted by the website's CrashReport button land here.
102+ # UI at http://localhost:8000 (override via GLITCHTIP_PORT if 8000 is taken).
103+ # On first boot, create an account and a project; the DSN goes into
104+ # APP_CONFIG.GLITCHTIP_DSN (see src/lib/env.ts).
105+ glitchtip-postgres :
106+ image : postgres:16-alpine
107+ environment :
108+ - POSTGRES_HOST_AUTH_METHOD=trust
109+ - POSTGRES_DB=glitchtip
110+ volumes :
111+ - glitchtip-pg:/var/lib/postgresql/data
112+ networks :
113+ - postguard
114+
115+ glitchtip-redis :
116+ image : valkey/valkey:8-alpine
117+ networks :
118+ - postguard
119+
120+ glitchtip-web :
121+ image : glitchtip/glitchtip:v5
122+ depends_on :
123+ - glitchtip-postgres
124+ - glitchtip-redis
125+ environment :
126+ - DATABASE_URL=postgres://postgres@glitchtip-postgres:5432/glitchtip
127+ - REDIS_URL=redis://glitchtip-redis:6379/0
128+ - SECRET_KEY=${GLITCHTIP_SECRET_KEY:-dev-only-change-me}
129+ - PORT=8001
130+ - DEFAULT_FROM_EMAIL=noreply@postguard.local
131+ - GLITCHTIP_DOMAIN=http://localhost:8001
132+ - ENABLE_OPEN_USER_REGISTRATION=true
133+ ports :
134+ - ' 8001:8001'
135+ volumes :
136+ - glitchtip-uploads:/code/uploads
137+ networks :
138+ - postguard
139+
140+ glitchtip-worker :
141+ image : glitchtip/glitchtip:v5
142+ command : ./bin/run-celery-with-beat.sh
143+ # `glitchtip-web` runs Django migrations on startup. Without
144+ # depending on it, the worker's celery-beat starts firing
145+ # scheduled jobs (uptime-dispatch-checks etc.) before the
146+ # tables exist and crashes with `relation "uptime_monitor"
147+ # does not exist`. The web container has no healthcheck, so
148+ # this is a `service_started` ordering rather than a true
149+ # readiness gate — good enough on a single host where the
150+ # web container reaches Postgres in a few seconds.
151+ depends_on :
152+ - glitchtip-postgres
153+ - glitchtip-redis
154+ - glitchtip-web
155+ - glitchtip-init
156+ environment :
157+ - DATABASE_URL=postgres://postgres@glitchtip-postgres:5432/glitchtip
158+ - REDIS_URL=redis://glitchtip-redis:6379/0
159+ - SECRET_KEY=${GLITCHTIP_SECRET_KEY:-dev-only-change-me}
160+ volumes :
161+ - glitchtip-uploads:/code/uploads
162+ networks :
163+ - postguard
164+
165+ # One-shot: applies migrations and creates the dev superuser if it
166+ # doesn't already exist. Runs to completion and exits. Idempotent —
167+ # `createsuperuser --noinput` errors on a duplicate, which we
168+ # swallow so subsequent `up`s remain no-ops. Override credentials
169+ # by exporting GLITCHTIP_ADMIN_EMAIL / GLITCHTIP_ADMIN_PASSWORD.
170+ glitchtip-init :
171+ image : glitchtip/glitchtip:v5
172+ depends_on :
173+ - glitchtip-postgres
174+ environment :
175+ - DATABASE_URL=postgres://postgres@glitchtip-postgres:5432/glitchtip
176+ - REDIS_URL=redis://glitchtip-redis:6379/0
177+ - SECRET_KEY=${GLITCHTIP_SECRET_KEY:-dev-only-change-me}
178+ # python-email-validator (used by GlitchTip's Pydantic response
179+ # schemas) rejects `local`, `localhost`, `test`, `invalid`,
180+ # `arpa`, `onion` TLDs as special-use — so `/api/0/users/me/`
181+ # 500s if the superuser email lands on one of those. `.dev` is
182+ # a real public TLD and not on that list.
183+ - DJANGO_SUPERUSER_EMAIL=${GLITCHTIP_ADMIN_EMAIL:-admin@postguard.dev}
184+ - DJANGO_SUPERUSER_PASSWORD=${GLITCHTIP_ADMIN_PASSWORD:-devpass1}
185+ # Bootstraps: migrations → superuser → PostGuard org → website
186+ # project → prints the DSN. Each step is idempotent so subsequent
187+ # `up`s remain no-ops. Paste the printed DSN into static/config.js
188+ # under `APP_CONFIG.GLITCHTIP_DSN` (or set `GLITCHTIP_DSN=…` in
189+ # .env.dev) — the DSN is stable as long as the postgres volume
190+ # survives, so this is a one-time copy.
191+ command :
192+ - sh
193+ - -c
194+ - |
195+ ./manage.py migrate --noinput &&
196+ (./manage.py createsuperuser --noinput 2>/dev/null \
197+ && echo 'glitchtip-init: superuser created' \
198+ || echo 'glitchtip-init: superuser already exists') &&
199+ ./manage.py shell -c "
200+ from django.apps import apps
201+ from django.contrib.auth import get_user_model
202+ Organization = apps.get_model('organizations_ext', 'Organization')
203+ Project = apps.get_model('projects', 'Project')
204+ ProjectKey = apps.get_model('projects', 'ProjectKey')
205+
206+ user = get_user_model().objects.get(email='$$DJANGO_SUPERUSER_EMAIL')
207+
208+ org, org_created = Organization.objects.get_or_create(
209+ slug='postguard',
210+ defaults={'name': 'PostGuard'},
211+ )
212+ if org_created:
213+ org.add_user(user)
214+ print('glitchtip-init: org PostGuard created')
215+ else:
216+ print('glitchtip-init: org PostGuard already exists')
217+
218+ project, proj_created = Project.objects.get_or_create(
219+ organization=org,
220+ slug='postguard-website',
221+ defaults={'name': 'postguard-website', 'platform': 'javascript'},
222+ )
223+ if proj_created:
224+ print('glitchtip-init: project postguard-website created')
225+ else:
226+ print('glitchtip-init: project postguard-website already exists')
227+
228+ # Pin a deterministic public_key so the DSN survives a
229+ # full volume wipe. Without this, a fresh postgres volume
230+ # regenerates the UUID and any hardcoded DSN in
231+ # static/config.js silently goes stale.
232+ #
233+ # GlitchTip auto-creates a default ProjectKey when a
234+ # Project is saved, so on a fresh volume the get_or_create
235+ # below returns that auto-created key (key_created=False)
236+ # and the defaults= clause is ignored. The `not key_created`
237+ # branch catches that case and rewrites public_key to
238+ # DEV_KEY.
239+ import uuid
240+ DEV_KEY = uuid.UUID('1d8ea2a49c904f079b116550780c0ece')
241+ key, key_created = ProjectKey.objects.get_or_create(
242+ project=project,
243+ defaults={'public_key': DEV_KEY},
244+ )
245+ if not key_created and key.public_key != DEV_KEY:
246+ key.public_key = DEV_KEY
247+ key.save()
248+ dsn = f'http://{key.public_key.hex}@localhost:8001/{project.id}'
249+ print('========================================')
250+ print('glitchtip-init: DSN (already set in static/config.js):')
251+ print(f' {dsn}')
252+ print('========================================')
253+ "
254+ restart : ' no'
255+ networks :
256+ - postguard
257+
94258 # Nginx reverse proxy
95259 nginx :
96260 image : nginx:alpine
@@ -114,3 +278,5 @@ volumes:
114278 cryptify-data :
115279 cryptify-target :
116280 postguard-pkg-target :
281+ glitchtip-pg :
282+ glitchtip-uploads :
0 commit comments