A self-hosted wardrobe manager that uses AI to catalogue your clothes and suggest outfits based on the weather.
Clean OAuth login — no passwords to manage
Your wardrobe at a glance — AI fills in the details automatically
Drop a photo and the AI takes care of the rest
Daily outfit suggestions matched to real weather data
Track what you wear, rate your outfits, and build a history over time
You upload a photo of a piece of clothing. An AI model running in the background figures out what it is — the category, colour, style, and what season it works for — and saves all of that to your wardrobe automatically. Once you have enough items, you can ask for an outfit recommendation. The app pulls today's real weather data and asks the AI to put together a combination of items that make sense for the conditions and occasion. Everything is saved locally, nothing goes to a third-party service, and you can run the whole thing with a single Docker command.
Key features:
- Photo-based wardrobe cataloguing with automatic AI tagging
- Daily outfit suggestions matched to live weather data from Open-Meteo
- Supports OpenAI, Google Gemini, Ollama (free, runs locally), or any OpenAI-compatible API
- Sign in with GitHub or Google — no passwords
- Outfit history with star ratings and wear tracking
- Fully self-hosted — your photos and data stay on your machine
git clone https://github.com/Gurshaan-Deol/hangar.git
cd hangar
cp .env.example .envOpen .env and fill in your credentials (see OAuth Setup and AI Configuration below), then:
docker compose up -d
docker compose exec backend alembic upgrade head| Service | URL |
|---|---|
| App | http://localhost:3000 |
| API docs | http://localhost:8000/docs |
You need credentials from at least one provider. Both are free.
- Go to github.com/settings/developers → OAuth Apps → New OAuth App
- Set Homepage URL to
http://localhost:3000 - Set Authorization callback URL to
http://localhost:3000/api/auth/callback/github - Copy the Client ID and generate a Client Secret
- Add to
.env:
GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret- Go to console.cloud.google.com → create a project → APIs & Services → Credentials → Create OAuth client ID
- Set application type to Web application
- Add
http://localhost:3000/api/auth/callback/googleas an authorised redirect URI - Copy the Client ID and Client Secret
- Add to
.env:
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secretYou only need one provider to get started.
Pick one provider and set the relevant block in your .env.
AI_PROVIDER=openai
AI_BASE_URL=https://api.openai.com/v1
AI_API_KEY=sk-...
AI_VISION_MODEL=gpt-4o
AI_TEXT_MODEL=gpt-4oAI_PROVIDER=google
AI_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai
AI_API_KEY=AIza...
AI_VISION_MODEL=gemini-1.5-pro
AI_TEXT_MODEL=gemini-1.5-proInstall Ollama, pull a vision-capable model, then point the app at it.
ollama pull gemma3:latestAI_PROVIDER=ollama
AI_BASE_URL=http://host.docker.internal:11434/v1
AI_API_KEY=not-needed
AI_VISION_MODEL=gemma3:latest
AI_TEXT_MODEL=gemma3:latestNote: smaller local models like gemma3 are slower and less accurate than GPT-4o or Gemini. Analysis can take 30–60 seconds per item on CPU.
┌─────────────────────────────────────────────────────────┐
│ Browser │
└─────────────────────┬───────────────────────────────────┘
│ HTTP
┌─────────────────────▼───────────────────────────────────┐
│ Frontend — Next.js 14 (:3000) │
│ • App Router + TypeScript │
│ • NextAuth.js v5 (GitHub / Google OAuth) │
│ • TanStack Query for server state │
│ • Proxies /api/v1/* to backend with auth token │
└─────────────────────┬───────────────────────────────────┘
│ REST API
┌─────────────────────▼───────────────────────────────────┐
│ Backend — FastAPI (:8000) │
│ • Async SQLAlchemy + Pydantic v2 │
│ • JWT auth via NextAuth secret │
│ • Clothing CRUD + file upload │
│ • Outfit recommendations + history │
└──────┬──────────────┬────────────────┬──────────────────┘
│ │ │
┌──────▼──────┐ ┌─────▼──────┐ ┌──────▼────────────────────┐
│ PostgreSQL │ │ Redis 7 │ │ arq Worker │
│ 15 │ │ │ │ • Picks up analysis jobs │
│ │ │ Job queue │ │ • Preprocesses images │
│ Users │ │ Weather │ │ • Calls AI provider │
│ Clothing │ │ cache │ │ • Updates item status │
│ Outfits │ │ │ │ • Retries on failure │
└─────────────┘ └────────────┘ └───────────────────────────┘
│
┌──────────▼────────────────┐
│ AI Provider │
│ OpenAI / Gemini / Ollama │
└───────────────────────────┘
Photo uploads are analysed by an arq background worker so the HTTP request returns immediately. The frontend polls the item's status field (pending → analyzing → ready) to show live progress without blocking.
| Layer | Technology |
|---|---|
| Frontend | Next.js 14, TypeScript, TanStack Query v5, Tailwind CSS, shadcn/ui |
| Backend | FastAPI 0.111, SQLAlchemy 2.0 (async), Pydantic v2, Python 3.11+ |
| Database | PostgreSQL 15 |
| Cache + Queue | Redis 7 + arq |
| Auth | NextAuth.js v5 — GitHub and Google OAuth |
| AI | Any OpenAI-compatible API |
| Weather | Open-Meteo — free, no API key needed |
| Deployment | Docker + Docker Compose |
| Variable | Required | Description |
|---|---|---|
NEXTAUTH_URL |
Yes | Frontend URL — http://localhost:3000 in dev |
NEXTAUTH_SECRET |
Yes | Random string, 32+ characters. Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" |
POSTGRES_USER |
Yes | PostgreSQL username |
POSTGRES_PASSWORD |
Yes | PostgreSQL password |
POSTGRES_DB |
Yes | PostgreSQL database name |
DATABASE_URL |
Yes | Full async connection string — must match the postgres vars above |
REDIS_URL |
Yes | Redis connection — redis://redis:6379 inside Docker |
GITHUB_CLIENT_ID |
OAuth | GitHub OAuth app client ID |
GITHUB_CLIENT_SECRET |
OAuth | GitHub OAuth app client secret |
GOOGLE_CLIENT_ID |
OAuth | Google OAuth app client ID |
GOOGLE_CLIENT_SECRET |
OAuth | Google OAuth app client secret |
AI_PROVIDER |
Yes | openai, google, or ollama |
AI_BASE_URL |
Yes | API base URL for your chosen provider |
AI_API_KEY |
Yes* | API key — use not-needed for Ollama |
AI_VISION_MODEL |
Yes | Model used for image analysis |
AI_TEXT_MODEL |
Yes | Model used for outfit recommendations |
WEATHER_LAT |
Yes | Default latitude for weather lookups |
WEATHER_LON |
Yes | Default longitude for weather lookups |
UPLOAD_DIR |
Yes | Path inside the container where photos are stored |
MAX_UPLOAD_SIZE_MB |
Yes | Maximum upload file size in MB |
ALLOWED_ORIGINS |
Yes | CORS allowed origins — http://localhost:3000 in dev |
Start all services with hot reload:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -dTail logs:
docker compose logs -f frontend backend workerRun tests:
docker compose exec backend pytest tests/ -v
docker compose exec frontend npm test -- --watchAll=falseLint and type check:
docker compose exec backend ruff check app/
docker compose exec frontend npx tsc --noEmitStop everything:
docker compose down- Docker Desktop
- 4 GB RAM minimum — 8 GB if running Ollama locally
- A GitHub or Google account for OAuth (both are free)
- An AI provider — Ollama runs entirely on your own hardware at no cost; OpenAI and Gemini are pay-per-use
Contributions are welcome. To get started:
git clone https://github.com/Gurshaan-Deol/hangar.git
cd hangar
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -dPlease open an issue before submitting a large PR. Commit messages follow Conventional Commits.
docker compose up fails with a database error
Run the migrations manually:
docker compose exec backend alembic upgrade headAI analysis always fails
Check the worker logs first:
docker compose logs worker --tail=50The most common causes are a wrong AI_API_KEY, a model that hasn't been pulled in Ollama, or AI_BASE_URL being unreachable from inside the Docker network. For Ollama, make sure you have run ollama pull gemma3:latest on the host machine before starting the stack.
OAuth login fails with a "Configuration" error
Check that NEXTAUTH_SECRET is at least 32 characters and that NEXTAUTH_URL exactly matches the URL you are using to access the app.
Photos not loading after upload
Photos are served through an authenticated API endpoint rather than as static files. If images aren't loading, make sure you are signed in and check the backend logs:
docker compose logs backend --tail=20