Docker Compose orchestration for the whole Beyou stack.
One command brings up Postgres, the Spring Boot API, and the web client — plus an optional observability overlay.
Beyou is a gamified personal-productivity app — habits, goals, routines, tasks, and categories, with XP and streaks on top. It lives across several repositories. This one contains no application code. It is the Compose layer that assembles the others into something you can actually run on your own machine: the dev stack with hot reload, an isolated stack for the Playwright suite, and an optional local observability overlay.
Production does not run from this repository. It runs on a single-node k3s cluster managed by Flux from be-infra — see Production.
- Composable overlays, not three separate stacks — a base file defines Postgres and the shared network;
the
devandmonitoringoverlays layer on top of it, ande2eis a self-contained stack with its own project name. The monitoring overlay is a local copy of the production observability stack (Prometheus, Loki + Alloy, Grafana, GlitchTip), not the same deployment — production's lives in be-infra as Kubernetes manifests. - Hot reload in dev — the backend and frontend source trees are bind-mounted; build artefacts
(
target/,node_modules/, the Maven repo) live in named volumes so host and container tooling never fight over the same directory. - Three observability questions, three answers — Prometheus for how is it performing, Loki for what happened, GlitchTip for what broke. All three surface in one Grafana.
- Loopback by default — every administrative surface (Grafana, Prometheus, GlitchTip, Alloy, the actuator)
publishes to
127.0.0.1only. Reach them through a tunnel or reverse proxy, not by widening the binding.
flowchart LR
subgraph app["beyou_net — application stack"]
fe["frontend<br/>Vite (dev)"]
be["backend<br/>Spring Boot"]
db[("postgres 15")]
fe --> be --> db
end
subgraph obs["monitoring overlay (optional)"]
prom["Prometheus"]
loki["Loki"]
alloy["Alloy"]
gt["GlitchTip"]
graf["Grafana"]
prom --> graf
loki --> graf
alloy -- push --> loki
end
be -- "/actuator/prometheus" --> prom
alloy -. "Docker API<br/>(all beyou* containers)" .-> app
fe -- "Sentry SDK" --> gt
be -- "Sentry SDK" --> gt
gt -- "uptime + heartbeat" --> be
subgraph gtnet["glitchtip_net — isolated"]
gtdb[("postgres 18")]
valkey[("valkey")]
end
gt --> gtdb
gt --> valkey
GlitchTip's web container is deliberately dual-homed: it needs beyou_net to poll the backend's management
port, but its own datastores stay off the application network.
- Docker Engine or Docker Desktop with the
docker composev2 plugin - Git
openssl(for generating secrets), only if you run the monitoring overlay
The dev overlay builds the backend and frontend from sibling directories, so the layout matters.
git clone https://github.com/AndDev741/Beyou-dev-env.git
git clone https://github.com/AndDev741/Beyou-backend-spring.git
git clone https://github.com/AndDev741/Beyou-Frontend.gityour-workspace/
├── Beyou-dev-env/ # you are here
├── Beyou-backend-spring/ # built by docker-compose.dev.yml
└── Beyou-Frontend/ # built by docker-compose.dev.yml
Important
Keep the checkout directory name starting with beyou. Compose derives the project name from it, and the log
collector only tails containers whose project name matches (?i)beyou.* — rename the directory and logs
silently stop flowing.
cd Beyou-dev-env
cp .env.example .env.env is gitignored. Every variable is documented inline in .env.example; the ones without a working default:
| Variable | Needed when | Notes |
|---|---|---|
TOKEN_SECRET |
always | JWT signing key, 32+ characters |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
Google sign-in | leave empty to disable |
MAIL_* |
verification & reset e-mail | SMTP credentials for the backend |
GRAFANA_ADMIN_PASSWORD |
--monitoring |
no default — Grafana refuses to start without it |
GLITCHTIP_SECRET_KEY |
--monitoring |
no default — openssl rand -hex 32 |
GLITCHTIP_DB_PASSWORD |
--monitoring |
no default, required on both ends of the connection |
Warning
The Postgres defaults (postgres / 1516) are local-development conveniences. Change them, along with
TOKEN_SECRET and DOCS_IMPORT_SECRET, before this stack is reachable by anything but your own machine.
./scripts/up-dev.shThen open http://localhost:3000. The API answers on http://localhost:8099/api/v1.
First boot compiles the backend and installs frontend dependencies into named volumes — expect a few minutes. Subsequent starts reuse both.
| Command | What it does |
|---|---|
./scripts/up-dev.sh |
Build and run the dev stack in the foreground, with hot reload |
./scripts/down.sh dev |
Stop the dev stack and remove orphans |
./scripts/reset-db.sh |
Tear down the dev stack and delete the Postgres volume |
./scripts/bootstrap-glitchtip.sh |
Create GlitchTip's org, projects, monitors, and alert rules |
Add --monitoring to up-dev.sh or down.sh to include the observability overlay:
./scripts/up-dev.sh --monitoring
./scripts/down.sh dev --monitoringNote
reset-db.sh deletes data irreversibly and fails loudly if it cannot find the volume, rather than printing
"complete" after removing nothing. If your project name differs from the directory name, set
COMPOSE_PROJECT_NAME so it looks in the right place.
Backend and frontend are built from the sibling checkouts at the dev stage of their Dockerfiles and run
against bind-mounted source. Container build output is kept in named volumes
(beyou_backend_target, beyou_maven_repo, beyou_frontend_node_modules) so a host-side ./mvnw or
npm run dev cannot corrupt the container's artefacts, or leave root-owned files behind.
The backend is capped at 2 CPUs / 1 GB with MaxRAMPercentage=70.
A self-contained stack under its own Compose project name (beyou-e2e) and its own network, so it can never
collide with a running dev stack. It uses the beyou_e2e database and SPRING_PROFILES_ACTIVE=e2e — the
backend's E2eSafetyCheck refuses to start against the dev database.
Build the images first, then bring it up:
docker compose -f docker-compose.e2e.yml up -d --waitThe Playwright specs live in Beyou-e2e-tests.
Production is not a Compose stack. It runs on a single-node k3s cluster, reconciled by Flux from be-infra:
| Path in be-infra | What lives there |
|---|---|
apps/beyou |
web, docs and backend Deployments, plus the backup CronJobs |
apps/glitchtip |
the production GlitchTip |
data/ |
the shared CloudNativePG Postgres cluster |
platform/monitoring |
kube-prometheus-stack, Loki, Alloy and Grafana, with e-mail alerting |
Deploys are Flux image automation: CI pushes ghcr.io/anddev741/beyou-*:latest, and Flux picks up the new
image and rolls it out. Nothing in this repository is involved, so a change here never reaches production.
| Service | Host port | Binding | Notes |
|---|---|---|---|
| Frontend | 3000 | all interfaces | Vite dev server |
| Backend | 8099 | all interfaces | API under /api/v1 |
| Postgres | 5490 | loopback | container port 5432 |
| Backend management | 9091 | loopback, --monitoring only |
/actuator/*, unversioned |
| Grafana | 3001 | loopback | login required |
| Prometheus | 9090 | loopback | query API is unauthenticated |
| GlitchTip | 8000 | loopback | see the first-run warning below |
| Alloy debug UI | 12345 | loopback | pipeline health, exposes /-/reload |
| Loki | — | none, deliberately | unauthenticated API; query it through Grafana |
| cAdvisor / node-exporter / postgres-exporter | — | none, deliberately | scraped over beyou_net only; ports overridable via CADVISOR_PORT / NODE_EXPORTER_PORT / POSTGRES_EXPORTER_PORT |
MONITORING_BIND and GLITCHTIP_BIND override the loopback default. Only do that if you know what you are
exposing and have something authenticating in front of it.
docker-compose.monitoring.yml is an optional overlay carrying Prometheus, Grafana, Loki + Alloy, and
GlitchTip. The division of labour:
| Question | Component | Retention |
|---|---|---|
| How is it performing? | Prometheus → Grafana | in-memory/TSDB defaults |
| What are the containers doing? | Prometheus (cAdvisor + node-exporter + postgres-exporter) → Grafana | in-memory/TSDB defaults |
| What happened? | Loki (fed by Alloy) | 30 days |
| What broke? | GlitchTip (Sentry-compatible) | GLITCHTIP_RETENTION_DAYS, 30 |
Four dashboards are provisioned into Grafana automatically: Beyou — Containers (fleet health: every container's resources, scrape targets, the host, the app DB and log/error volume), Beyou — Service Health (consolidated) (backend JVM internals), Beyou — AI Agent, and Beyou Logs.
Zero application configuration: anything a container writes to stdout is collected. Alloy tails every container in the Beyou Compose projects through the Docker API and pushes to Loki. That covers the Spring backend, the frontend, Postgres, and the monitoring services themselves — and the e2e stack too, when it runs.
Start in Grafana → Dashboards → Beyou Logs (volume and error charts, plus a filterable browser), or query ad-hoc in Explore:
{service="backend"} # everything the backend printed
{service="backend"} | detected_level="error" # errors only
{service=~"backend|frontend"} |~ "(?i)nullpointer" # regex across services
{project="beyou-e2e"} # the e2e stack, when it runs
detected_level is attached server-side by Loki, which parses JSON, logfmt, and plain-text keywords — neither
the apps nor Alloy do any log parsing.
Behaviour worth knowing before you trust it
- Scope — Alloy keeps only containers whose Compose project name starts with
beyou(case-insensitive). This host runs unrelated stacks, and their logs do not belong in a Loki that anyone with Grafana access can query. Deploy from a directory not starting withbeyouand logs silently stop; fix the directory name or thekeeprule inmonitoring/alloy/config.alloy. - Retention — 30 days, enforced by Loki's compactor (
monitoring/loki/loki.yml). History lives in theloki_datavolume;docker logson the host only holds the rotated tail (10 MB × 3 files per container, set in every compose file). - Exposure — Loki has no host port. Its push and query APIs are unauthenticated, so its only clients are
Alloy and Grafana over
beyou_net, and the read path goes through the component that has a login. - Own liveness — Prometheus scrapes both (
up{job="loki"},up{job="alloy"}), because a dead log pipeline fails silent: ingestion just stops. Alloy buffers and retries while Loki is down, but the buffer is finite —loki_write_dropped_entries_totalon thealloyjob is the "logs were actually lost" signal. - Not captured — logs from real users' browsers. That is GlitchTip's job. Do not close the gap by exposing a Loki push endpoint to browsers; an unauthenticated ingest path open to the internet is an abuse surface.
Three exporters in the monitoring overlay feed the Beyou — Containers dashboard. None publishes a host port:
their only clients are Prometheus and Grafana over beyou_net, same as Loki.
| Exporter | What it measures | Job |
|---|---|---|
cAdvisor (ghcr.io/google/cadvisor) |
Per-container CPU, memory, network, disk, uptime, labeled by Compose service/project | cadvisor |
node-exporter (prom/node-exporter) |
The host: CPU, load, memory, root disk, network | node-exporter |
postgres-exporter (prometheuscommunity/postgres-exporter) |
The app DB from the DB's own side: connections, cache hit ratio, deadlocks, locks, size (pg_up is the "DB answering" signal) |
postgres-exporter |
Container ports are CADVISOR_PORT / NODE_EXPORTER_PORT / POSTGRES_EXPORTER_PORT (defaults 8080/9100/9187);
the config template reads the same variables (monitoring/prometheus/prometheus.yml.tpl), so mapping them
differently changes one line in .env. Grafana also exposes its own /metrics (GF_METRICS_ENABLED, job
grafana).
The dashboard scopes cAdvisor data to Compose projects whose name starts with beyou — this host runs unrelated
stacks, and their containers do not belong on the fleet board. Same rule as the Alloy log filter.
GlitchTip speaks the Sentry API, so the official Sentry SDKs in the backend, web, and mobile clients point at it by DSN. It also owns the uptime and heartbeat monitors, because it is the component that can notify a human.
Everything in this section is about the local GlitchTip in the monitoring overlay. Production's GlitchTip
(mnt.beyouweb.com) runs in be-infra (apps/glitchtip), and its monitors point at the cluster's Services, not at
the container names used here.
Caution
GlitchTip lets the first account self-register as admin regardless of ENABLE_USER_REGISTRATION. Between
up and the moment you register, whoever can reach port 8000 owns the collector — every error event and every
uploaded source map. That is why the port is loopback-bound, and why you should register immediately.
There is one supported path: register the admin account in the UI, then run the bootstrap script.
-
Set
GLITCHTIP_SECRET_KEY(openssl rand -hex 32) and the rest of theGLITCHTIP_*block in.env. The container will not boot without the key. -
Start with
--monitoring. First boot runs migrations and takes a minute or two. -
Open http://localhost:8000, click Register, and create the account. This is the one step the script deliberately does not do — registration sets a password, which a bootstrap script has no business holding. With
GLITCHTIP_ENABLE_USER_REGISTRATION=False(the default), self-signup closes as soon as it exists. -
Run the script:
./scripts/bootstrap-glitchtip.sh
It creates the organization and team, one project per reporting surface (
beyou-backend,beyou-web,beyou-mobile) plus an infrastructure project (beyou-infra) that hosts the container monitors, one uptime monitor per Beyou container plus the two public pages, the snapshot heartbeat, and one alert rule per project with an e-mail recipient wired to it. It then prints the DSNs, the heartbeat check-in URL, and which address alerts will actually reach. -
Paste the printed DSNs into the corresponding app env vars, and
SNAPSHOT_HEARTBEAT_URL(plusNUDGE_HEARTBEAT_URL, when the nudge monitor was created) into this repo's.env.
The script reads the GLITCHTIP_*_TARGET variables (one per uptime monitor; defaults match the compose stack's
CONTAINER addresses) plus GLITCHTIP_ALERT_EMAIL, and overwrites them in the collector on every run, so a
wrong value here beats a right value in the UI. Full list in .env.example; the ones you are most likely to
touch:
GLITCHTIP_FRONTEND_TARGET=frontend:3000 # the dev stack's Vite port, also the script's default
GLITCHTIP_ALERT_EMAIL=ops@example.com # empty = the account you registered in step 3Tip
Re-running is safe, and not a no-op: creation is get_or_create, but every run re-applies the script's own
values over whatever is in the database, so drift is repaired rather than reported as "already present". It
prints reconciled … for anything it corrected, and never creates duplicates.
Do not create the organization, projects, or alert rules by hand as well. That is how you end up with DSNs pointing at one project and alert rules attached to another, so events arrive somewhere nothing is watching.
Storing errors and turning a monitor red announces nothing. Two GlitchTip code paths do the announcing, and
both go silent unless an AlertRecipient row exists:
| Path | Trigger | Requires |
|---|---|---|
| Errors | apps.alerts.tasks.process_event_alerts, every 60s |
a rule with quantity and timespan_minutes |
| Uptime + heartbeat | apps.uptime.tasks.send_monitor_notification, on state change |
a rule on the monitor's project with uptime = True |
The bootstrap script creates one rule per project covering both paths — one and not two on purpose, because
the uptime lookup joins through the project, so a second uptime = True rule mails every monitor event twice.
The channel is e-mail over the same SMTP the backend already uses: nothing new to provision, and that account is
known to deliver. Compose GLITCHTIP_EMAIL_URL from the same MAIL_* credentials — .env.example documents the
exact scheme, the STARTTLS-vs-implicit-TLS choice, and the four encoding traps that silently break it.
consolemail:// stays the default so a dev machine does not start mailing. It is not a stub — the message is
fully composed and printed:
docker logs -f beyou-dev-env-glitchtip-1 # look for "Subject: Error in ..."Important
The recipient is not the address on the rule. GlitchTip ignores that field for e-mail and resolves every
user on the team that owns the project. That is why GLITCHTIP_ALERT_EMAIL is applied by the script rather
than by Compose — it has to make the address a user, add it to the organization, and add it to the team. The
account is created with an unusable password: a mailbox, not a login.
Two consequences: it is additive (the account from step 3 keeps receiving alerts — remove it from the team or
set its per-project alerts to Off to stop that), and a per-project Off beats everything the script does. The
script will not overwrite a human's opt-out, but it prints a WARNING naming the user and projects, because
otherwise the rule looks wired and mails nobody.
Reference record — the alert rule, by hand
Under Settings → Projects → <project> → Alerts, for each of the four projects:
| Field | Value |
|---|---|
| Name | New issue |
| Send notification when | 1 event in 1 minute |
| Also alert on uptime check failures | yes |
| Recipient | Email (no URL — see above) |
Names must match scripts/bootstrap-glitchtip.py exactly; the script keys rules on (project, name) and will
otherwise create a second one alongside yours.
One monitor per Beyou container, plus the heartbeat, all created by the bootstrap script — none by Compose.
GlitchTip monitors live in its database, so a retention wipe, a volume reset, or a move to a new host means
re-running the script. App monitors sit on the app projects; the container monitors sit on beyou-infra, which
has its own alert rule — one rule per project, so a monitor event is never mailed twice.
| # | Monitor | Type | Question it answers |
|---|---|---|---|
| 1 | Beyou backend health |
GET http://backend:9091/actuator/health/uptime |
Is the process answering? |
| 2 | Snapshot scheduler heartbeat |
Heartbeat, 5400s | Is the scheduled job still running? |
| 2b | Engagement nudge heartbeat |
Heartbeat, 5400s | Is the nudge pass still running? Opt-in: set GLITCHTIP_NUDGE_HEARTBEAT=1 and re-run the bootstrap in the same change that sets ENGAGEMENT_NUDGES_ENABLED=true. The pass returns before checking in while the flag is off, so a monitor created earlier pages every 90 minutes about a job that is off on purpose |
| 3 | Beyou web frontend |
TCP frontend:3000 |
Is the app being served at all? |
| 4 | Beyou postgres (app DB) |
TCP db:5432 |
Is the database accepting connections? |
| 5 | Beyou glitchtip postgres |
TCP glitchtip-db:5432 |
Is the collector's DB answering? |
| 6 | Beyou glitchtip valkey |
TCP glitchtip-valkey:6379 |
Is the cache accepting connections? |
| 7 | Beyou loki |
GET http://loki:3100/ready |
Is the log store ready? |
| 8 | Beyou alloy |
GET http://alloy:12345/-/healthy |
Is the whole log pipeline healthy? (500 = any component unhealthy) |
| 9 | Beyou prometheus |
GET http://prometheus:9090/-/healthy |
Is the metrics store answering? |
| 10 | Beyou grafana |
GET http://grafana:3000/api/health |
Is Grafana's own DB healthy? |
| 11 | Beyou glitchtip (self) |
GET http://glitchtip:8000/_health/ |
Is the collector answering? (for the Uptime page, not alerts) |
All infra monitors use the same tuning as the backend one: 60s interval, 10s timeout, confirmation threshold 2 (two consecutive failures before alerting — one dropped poll during a restart is not an outage). The TCP monitors need no HTTP endpoint and carry the port inside the URL field, exactly like the frontend monitor.
Monitor 2 is the interesting one. The health endpoint keeps returning 200 while RoutineSnapshotScheduler quietly
stops writing daily snapshots — nothing fails, data just stops appearing. So the check is inverted: the backend
checks in after every completed hourly cycle, and GlitchTip alerts on the check-in not arriving.
Reference record — full monitor settings and rationale
The names must match
scripts/bootstrap-glitchtip.pyexactly. The script keys every monitor on itsnameviaget_or_create. Create one by hand under a different name and you get two monitors polling the same thing — and the hand-made one has none of theconfirmation_thresholdtuning, so it alerts on a single dropped poll.
1. Backend uptime
| Field | Value |
|---|---|
| Name | Beyou backend health |
| Project | beyou-backend |
| Monitor type | GET |
| URL | http://backend:9091/actuator/health/uptime |
| Expected status | 200 |
| Interval / timeout | 60 / 10 seconds |
| Confirmation threshold | 2 — one dropped poll during a restart is not an outage |
The path is the uptime health group, not the bare /actuator/health. The bare endpoint aggregates every
indicator Boot registers, and an indicator is free to make a network call: the mail one used to open a live
authenticated SMTP session to Gmail on every poll, which cost ~550ms against ~4ms for a group — with no ceiling,
because JavaMail's default timeout is infinite. That put a third party's latency in front of the 10s timeout
above, and a slow Gmail read as a dead backend. The group is an allowlist (db,diskSpace,ping) defined in the
backend's application.yaml; keep the two in step, because a renamed group leaves this monitor probing a 404.
Not Boot's own readiness group: that one contains only readinessState, which is UP the moment the app has
started and would not notice a dead database.
Use the in-network URL, not localhost: inside the GlitchTip container localhost is GlitchTip. The management
server binds 127.0.0.1 by default; this stack sets MANAGEMENT_ADDRESS=0.0.0.0 so the container's port is
reachable within beyou_net, and the overlay publishes it only to the Docker host.
GLITCHTIP_UPTIME_ALLOW_PRIVATE_IPS=True is already set — without it the SSRF guard rejects RFC1918 targets and
the monitor cannot be saved.
2. Snapshot job heartbeat
| Field | Value |
|---|---|
| Name | Snapshot scheduler heartbeat |
| Project | beyou-backend |
| Monitor type | Heartbeat |
| Expected interval | 5400 seconds (90 min) |
| Confirmation threshold | 1 — the interval already carries 30 minutes of slack |
The job runs hourly (@Scheduled(cron = "0 0 * * * *")) and checks in after every completed cycle, not only
the midnight ones, so a stalled job is caught within the hour instead of within a day. Saving the monitor
produces a check-in URL; put it in .env as SNAPSHOT_HEARTBEAT_URL (using the in-network host
glitchtip:8000) and restart the backend. Leaving it empty disables the heartbeat entirely.
- A run that throws sends nothing, so a broken job shows as a missing heartbeat rather than a green light.
- A failed check-in never fails the snapshot job — it is logged at WARN and the cycle carries on. Monitoring must not become the cause of the outage it watches.
- Startup backfill does not check in; otherwise a crash-restart loop would keep the monitor green forever.
- The
testande2eprofiles pin the URL empty.
3. Web frontend
| Field | Value |
|---|---|
| Name | Beyou web frontend |
| Project | beyou-web |
| Monitor type | TCP Port |
| URL | frontend:3000 |
| Interval / timeout | 60 / 10 seconds |
| Confirmation threshold | 2 |
A healthy API behind a frontend that stopped being served is still a total outage, and the two monitors above
would not notice. TCP Port monitors carry the port inside the URL field — GlitchTip splits on : and hands
both halves to asyncio.open_connection. There is no separate port input, and Expected status is ignored.
Getting the port wrong is worse than having no monitor: it leaves a permanently red light nobody believes. The
script defaults to the dev stack's frontend:3000; override GLITCHTIP_FRONTEND_TARGET only if you serve the
frontend some other way. This is a TCP connect, not an HTTP fetch — it proves something is accepting
connections, not that the app renders.
Every alarm above is raised by GlitchTip. So GlitchTip cannot be what tells you GlitchTip is down: when it wedges, every signal goes quiet at once and the stack looks calm. Two mechanisms cover that:
- Compose healthcheck on the
glitchtipservice, running the image's own/code/healthcheck.py.docker psthen reportsunhealthyinstead of a misleadingUp. Allow up to the 120sstart_periodon first boot. - Prometheus scrape of
glitchtip:8000/metricsas jobglitchtip(django-prometheus, switched on byENABLE_OBSERVABILITY_API).up{job="glitchtip"}is a liveness signal owned by the one component that is not the collector.
Warning
Both are still look-at-it signals. Everything GlitchTip watches announces itself by e-mail; collector
death does not. Prometheus scrapes up{job="glitchtip"} and nothing evaluates it, and the healthcheck only
changes what docker ps prints.
The gap is deliberate. Closing it properly means either an Alertmanager (alerting: + rule_files: in
monitoring/prometheus/prometheus.yml.tpl, with up{job="glitchtip"} == 0 as the first rule) or Grafana alerting —
a second notification system to own, consciously not introduced alongside the first. Until then, treat
"GlitchTip has gone quiet" as a thing to check, not a thing to trust.
Production backups are not done from this repository. They are in-cluster CronJobs in be-infra
(apps/beyou/backup.yaml): a nightly pg_dump plus the uploads, shipped to an encrypted restic repository on
Cloudflare R2, a weekly restore drill into a throwaway Postgres, and heartbeats to production's GlitchTip so a
job that silently stops is noticed.
The local dev database is not backed up at all. Treat it as disposable — that is what reset-db.sh is for.
| Symptom | Likely cause |
|---|---|
| Grafana container exits immediately | GRAFANA_ADMIN_PASSWORD is unset — it has no default, on purpose |
| GlitchTip container exits immediately | GLITCHTIP_SECRET_KEY or GLITCHTIP_DB_PASSWORD is unset |
| GlitchTip unhealthy for the first minute or two | Normal — migrations run before it starts serving |
| No logs in Grafana | Checkout directory name does not start with beyou; check the keep rule in monitoring/alloy/config.alloy |
| Frontend monitor permanently red | GLITCHTIP_FRONTEND_TARGET does not match the frontend container's port (3000 in dev) |
| Alerts never arrive | No AlertRecipient row, or a per-project Off — re-run the bootstrap script and read its WARNING lines |
| SMTP works elsewhere but not for GlitchTip | Quoted value, unencoded @ in the username, or an undoubled $ — see the notes in .env.example |
reset-db.sh says the volume was not found |
Project name differs from the directory name; set COMPOSE_PROJECT_NAME |
| Backend cannot reach Postgres in dev | Do not re-declare the db port in docker-compose.dev.yml — the base file publishes it |
| Repository | Contents |
|---|---|
| Beyou-backend-spring | Spring Boot API — domain model, gamification, auth, AI |
| Beyou-Frontend | Turborepo monorepo — React web app, Expo mobile app, shared packages |
| Beyou-e2e-tests | Playwright suite driving the full stack |
| Beyou-arch-design | OpenAPI specs and bilingual architecture docs |
| Beyou-docs-ui | Viewer app for the architecture and design documentation |
