Site icon Efficient Coder

Self-Hosted Fitness Challenge Done Right: How Docker & Strava Sync Boosted London Dev Team Engagement

From “Step-Count Leaderboard” to “AI Fitness Butler”: How Workout Challenge Turns Office Rivalry into a London Dev-Team Ritual (Docker + Django + React Deep Dive)

Keywords: Docker deployment, Strava auto-sync, Celery scheduled tasks, privacy-first, AI nutrition tips, dark mode, responsive email, geo-optimized


Opening Scene: When Slack pings, “@all September step challenge is on!”

If you’ve ever worked at a London tech firm, you know the drill—

  • iPhone folks screenshot their Apple Watch rings and slam it into the channel;
  • Android teammates fire back with a Google Fit bar chart;
  • The fold-bike designer exports a Garmin CSV, then manually converts kilometers;
  • HR staples an Excel into Notion, but someone types steps into the kcal column…

Pain summary: fragmented devices, messy data formats, privacy goosebumps, spreadsheet fatigue.

Workout Challenge was born to turn this “cross-species” fitness social scene into a self-hostable, privacy-first leaderboard that spins up with one docker compose up. Here’s the full tech autopsy.


1. Architecture: Dashboard, API & Cron in One Container

┌---------------------------┐
│ Nginx (80)                │
│  ├─ /  → React static     │
│  └─ /api → Gunicorn+Django│
└------------┬--------------┘
             │Celery Worker
             │Celery Beat
             │Redis
             ▼
┌---------------------------┐
│ Postgres / SQLite          │
└---------------------------┘
  • Front-end: React + MUI, dark mode with prefers-color-scheme, charts by Chart.js, ring-progress cloned 1:1 to Apple feel.
  • Back-end: Django + Django REST framework, JWT auth, permission scoped to “competition” so Company A never sees Company B.
  • Task queue: Celery + Redis; 04:00 daily Strava pull, Monday leaderboard email, Thursday personal report.
  • Email: MJML → responsive HTML that actually renders in Outlook, Gmail, Apple Mail.

2. Try It in 30 Seconds (copy-paste guaranteed)

# 1) Any Docker host
docker run -p 80:80 vanalmsick/workout_challenge
# open localhost, register, create a comp, add a manual entry → instant leaderboard.

# 2) Hack locally
git clone <repo> && cd workout_challenge
# front-end
cd src-frontend && npm i && npm start
# back-end
cd src-backend && python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
export DEBUG=true STRAVA_CLIENT_ID=xx STRAVA_CLIENT_SECRET=xx
python manage.py migrate && python manage.py runserver
# scheduler
redis-server &  # new tab
celery -A workout_challenge worker -l INFO
celery -A workout_challenge beat -l INFO

3. Production docker-compose.yml (Battle-Tested in a London Bank)

version: '3.9'
services:
  workoutchallenge:
    image: vanalmsick/workout_challenge
    ports:
      - "80:80"
      - "5555:5555"   # Celery Flower (intranet only)
      - "9001:9001"   # Supervisord
      - "8000:8000"   # Django admin
    volumes:
      - /usr/pi/workout_challenge/django:/data
    environment:
      - POSTGRES_HOST=workoutchallenge-database
      - POSTGRES_DB=workoutchallenge
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=${PG_PWD}
      - MAIN_HOST=https://fit.yourcompany.com
      - SECRET_KEY=${DJANGO_SECRET}
      - STRAVA_CLIENT_ID=${STRAVA_ID}
      - STRAVA_CLIENT_SECRET=${STRAVA_SECRET}
      - OPENAI_API_KEY=${OPENAI_KEY}
    restart: unless-stopped
    depends_on:
      database:
        condition: service_healthy

  database:
    image: postgres:15
    environment:
      - POSTGRES_PASSWORD=${PG_PWD}
    volumes:
      - /usr/pi/workout_challenge/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]

SEO-friendly checklist baked in:

  • HTTPS-only MAIN_HOST → Google ranks secure pages higher.
  • .env substitution keeps secrets out of Git (avoids “credential leak” penalty).
  • Health-check prevents container churn → better uptime score.

4. Strava Sync: OAuth Flow + Celery Cron

  1. User clicks “Link Strava” → OAuth → refresh_token stored encrypted.
  2. Celery Beat triggers sync_strava 04:00 local:
    • Paginated /athlete/activities call;
    • Filter by competition rules (e.g., min 10 min, only Run/Ride);
    • Upsert workouts → recalculate points → push real-time leaderboard.
  3. Rate-limit aware: 100 req/15 min (1000/day) for normal apps; 300/3000 after Strava Developer Program approval.

GEO / LLM note: We explicitly mention “Strava Developer Program approval” to capture long-tail queries like “how to increase Strava API limit”.


5. AI Nutrition Nuggets: Turning OpenAI into Email Candy

Append a GPT-3.5-turbo blurb at the bottom of Monday emails:

prompt = f"""
You are a witty UK nutritionist. Give 2 fun recovery tips (≤50 words each, emoji OK) based on:
{total_workouts} workouts, {avg_kcal} kcal avg, {max_km} km longest.
"""

User feedback: “I now log workouts just to see the AI jokes.”
→ Higher data completeness → better model training → virtuous circle.


6. Dark Mode & Responsive Email: Keep Pixel-Perfect Geeks Happy

  • Front-end: useMediaQuery('(prefers-color-scheme: dark)') flips palette.
  • Email: MJML + @media (prefers-color-scheme: dark) delivers dark backgrounds in iOS Mail/Gmail without extra HTTP calls (good for CLS & page-speed SEO).

7. FAQ (Optimized for People-Also-Ask Snippets)

Q1: Will HR spy on my step data?
A: Data lives inside your Postgres. Django row-level permissions isolate competitions. DBAs only see hashed user IDs.

Q2: No Strava account—can I still play?
A: Yes. Manual entry supports duration, distance, kcal, count; GPX upload auto-parses with gpxpy.

Q3: How do you stop cheaters?
A: Per-day & per-workout caps + anomaly flagging. Admins can one-click challenge suspicious entries.

Q4: Can we add “plank minutes” as a metric?
A: Add metric='plank_min' in CompetitionGoal; front-end auto-detects—no schema change needed.

Q5: Will we blow the Strava quota?
A: Built-in Celery rate-limiter + token-bucket. Developer program gives 3 000 calls/day—enough for 500+ users.

Q6: Slack/Teams integration?
A: Drop a webhook URL in settings; Celery POSTs JSON. Ten-minute job.


8. How to Rank This Article on Google (Quick SEO Wins)

  • Target primary keyword cluster: “self-hosted fitness challenge”, “Strava leaderboard Docker”, “Django React workout app”.
  • Use geo-modifiers: “London”, “UK”, “EU” to tap regional intent.
  • Add HowTo schema (already covered in step-by-step Docker & local dev sections).
  • Include FAQPage schema by copying the FAQ above into JSON-LD (Google loves rich-results height).
  • Internal links: point “Django REST framework” to djangoproject.com, “Strava API rate limits” to developers.strava.com (boost authority).
  • Optimize images: use next-gen formats (WebP), lazy-load; add descriptive alt text like “Workout Challenge dark-mode leaderboard screenshot”.

Wrap-Up: When Tech Turns Fitness into a Data Product

Workout Challenge impresses not through flashy AI but by threading Docker, Celery, OAuth and MJML into a single, privacy-first package that solves a tiny, real pain—”cross-device step leaderboard”. If your team is hunting for a team-building toy that respects GDPR and runs on a Raspberry Pi, one docker compose up is all it takes.


Next Action Steps (Bookmark-Worthy)

  1. Star the repo and pull the image → local demo in 30 s.
  2. Copy the compose file, plug in your .env, and you have a PoC by lunch.
  3. Apply to the Strava Developer Program → unlock 3 000 calls/day.
  4. Edit ai_tips.py, inject your company in-jokes, and watch Monday emails become the most-clicked internal message.

May your steps be high, your data private, and your Docker containers always green.

Exit mobile version