Troubleshooting
This guide covers common issues encountered when deploying and running Dharini.
Docker Issues
Section titled “Docker Issues”Containers Won’t Start
Section titled “Containers Won’t Start”Problem: Services fail to start or immediately exit
Check service status:
docker ps -adocker logs backenddocker logs frontendCommon causes:
-
Port conflicts - Another service using ports 3000 (frontend), 3002 (mobile-web), 4000 (backend), 5432 (PostgreSQL) or 6379 (Redis)
Terminal window # Check what's using a portsudo lsof -i :4000# Change ports in docker-compose.yml if needed -
Environment variables missing
Terminal window # Verify .env file exists and has required variablescat .env | grep DB_HOST -
Docker daemon not running
Terminal window sudo systemctl status dockersudo systemctl start docker
Backend Container Restarting
Section titled “Backend Container Restarting”Problem: Backend container in restart loop
Check logs:
docker logs backend --tail 100Common issues:
-
Database connection failed
- Verify
DB_HOST,DB_PORT,DB_USERNAME,DB_PASSWORDin.env - For RDS, ensure SSL is enabled (automatic in production)
- Check RDS security group allows EC2 access
- Verify
-
Missing required variables or failed migration - see Backend Fails to Start below.
-
Redis connection failed
- Verify Redis container is running:
docker ps | grep redis - Check
REDIS_HOST=redisin.env
- Verify Redis container is running:
Backend Fails to Start
Section titled “Backend Fails to Start”Problem: The backend exits at startup, or stays “Up” but nothing answers on port 4000.
The backend reads some variables at startup and refuses to run without them. All variables are described in Environment Variables. Startup order outside development: run migrations, build the app, check CORS_ORIGINS, run the seeder, start listening.
Required variables that stop the backend:
| Variable | Symptom in docker logs backend | Container state |
|---|---|---|
JWT_SECRET | Configuration key "JWT_SECRET" does not exist | Exits, restarts in a loop |
S3_BUCKET | same message naming S3_BUCKET | Exits, restarts in a loop |
PIPELINE_URL, PIPELINE_CALLBACK_URL, WEBHOOK_SECRET | same message naming the missing key | Exits, restarts in a loop |
QUEUE | Queue/Redis dependency-injection error at startup (the name of the job queue must be set) | Exits, restarts in a loop |
CORS_ORIGINS | CORS_ORIGINS must be set outside development | Stays “Up” with nothing listening (see below) |
PIPELINE_API_KEY is optional. CORS_ORIGINS must be a comma-separated list of bare origins such as https://dharini.example.org. A path, a trailing slash after a path, or * is rejected with a CORS_ORIGINS: ... is not a valid origin error.
Stays “Up” but nothing listens. The CORS_ORIGINS check, and any error thrown by the data seeder, happen after the process has started. The error is logged, but the process is not stopped, so Docker does not restart the container and requests to port 4000 are refused. We have not run this case end to end; this is read from main.ts. The container’s Docker healthcheck calls /health, so it is reported as unhealthy once the 300-second start period has passed, and ./deploy.sh (which waits for healthy containers) fails. Check the logs and the API itself:
docker logs backend --tail 50curl -i http://localhost:4000/health/health should return 200 with {"status":"ok", ...}.
Fix: set the missing variable in .env, then recreate the container so it reads the new value (a plain restart does not re-read env_file):
docker compose --profile production up -d backendMigration failed: the log shows Migration failed followed by the database error, and the backend exits. See Migration Failures.
Frontend Container Unhealthy
Section titled “Frontend Container Unhealthy”Problem: Frontend shows as “unhealthy” status
This is often normal during:
- Initial start
- After an update
- Server Action cache mismatches
Solutions:
-
Wait 1-2 minutes - Health check may pass after warm-up
-
Restart frontend:
Terminal window docker compose --profile production restart frontend -
Clear browser cache - Hard refresh (
Ctrl+Shift+R) -
Recreate all containers:
Terminal window docker compose --profile production --profile bundled-db down./deploy.sh production
Database Issues
Section titled “Database Issues”Cannot Connect to RDS
Section titled “Cannot Connect to RDS”Problem: “no pg_hba.conf entry for host” or connection timeout
Solutions:
-
Use SSL for RDS connections:
Terminal window PGSSLMODE=require psql -h your-rds-endpoint -U postgres -d postgres -
Check security group:
- RDS security group must allow port 5432 from EC2
- Verify EC2 security group ID or private IP is allowed
-
Verify RDS endpoint:
Terminal window # Test DNS resolutionnslookup your-database-instance.xxxxxxxxx.region.rds.amazonaws.com# Test connectivitync -zv your-database-instance.xxxxxxxxx.region.rds.amazonaws.com 5432 -
Check credentials:
- Verify username and password are correct
- Try connecting from another tool (pgAdmin, DBeaver)
PostGIS Extension Missing
Section titled “PostGIS Extension Missing”Problem: Migrations fail with “type ‘geometry’ does not exist”
Solution:
Connect to RDS and install PostGIS:
CREATE EXTENSION IF NOT EXISTS postgis;CREATE EXTENSION IF NOT EXISTS postgis_topology;CREATE EXTENSION IF NOT EXISTS "uuid-ossp";Migration Failures
Section titled “Migration Failures”Problem: The backend exits at startup with Migration failed in the logs.
Migrations run automatically every time the backend starts, in every environment except NODE_ENV=development (which uses schema sync instead). There is no separate migration command to run. A failed migration stops the backend, so it restarts in a loop until the cause is fixed.
Check:
-
Read the error:
Terminal window docker logs backend 2>&1 | grep -B2 -A15 "Migration failed" -
Verify database connection:
Terminal window docker exec -it backend env | grep DB_ -
See which migrations have run:
SELECT id, "timestamp", name FROM migrations ORDER BY id DESC LIMIT 10; -
Common causes:
- PostGIS extension missing (see above)
- Database user lacks permission to create tables or extensions
- A previous run was interrupted, or the schema was changed by hand, so a migration conflicts with objects that already exist
Fix: correct the cause, then restart the backend. It will run the pending migrations again. Each migration runs in a transaction, so a failed one normally leaves no partial changes. Take a database backup before editing the migrations table by hand; only do that if you know the migration’s changes are already in place.
Network and Nginx Issues
Section titled “Network and Nginx Issues”502 Bad Gateway
Section titled “502 Bad Gateway”Problem: Nginx returns 502 error
Causes:
-
Backend not running:
Terminal window docker ps | grep backenddocker logs backend -
Wrong upstream address in Nginx config:
Terminal window # The containers publish ports on the host, so 127.0.0.1 should workcurl -i http://127.0.0.1:4000/health# Update nginx config to matchsudo nano /etc/nginx/sites-available/dharini-prodIf the backend container is “Up” but this
curlis refused, the backend did not finish starting. See Backend Fails to Start. -
Firewall blocking ports:
Terminal window # Check if ports are listeningsudo netstat -tulpn | grep -E ':(3000|3002|4000)'
504 Gateway Timeout
Section titled “504 Gateway Timeout”Problem: Requests time out
Solutions:
-
Increase Nginx timeout:
location /api/ {proxy_read_timeout 300;proxy_connect_timeout 300;proxy_send_timeout 300;# ... other settings} -
Check backend performance:
Terminal window docker stats backend
SSL Certificate Errors
Section titled “SSL Certificate Errors”Problem: Certificate not working or expired
Solutions:
-
Test renewal:
Terminal window sudo certbot renew --dry-run -
Force renewal:
Terminal window sudo certbot renew --force-renewal -
Regenerate certificate:
Terminal window sudo certbot --nginx -d your-domain.com
API 404 Errors
Section titled “API 404 Errors”Problem: /api/ requests return 404
Check:
-
Nginx path configuration:
# Strips /api/ prefixlocation /api/ {proxy_pass http://127.0.0.1:4000/; # Trailing slash important!} -
Backend routes:
- The backend has no
/apiprefix. Its routes are/health,/users, and so on. - Nginx strips
/api/before forwarding.
- The backend has no
-
Test directly:
Terminal window # Bypass Nginx - the public health checkcurl http://localhost:4000/healthOther routes such as
/usersreturn401 Unauthorizedwithout a token, because every route requires login except/health, the login/OTP routes and the pipeline webhook. A401from the backend means it is running; it is not an error in itself.
Browser CORS Errors
Section titled “Browser CORS Errors”Problem: The browser console shows blocked by CORS policy or No 'Access-Control-Allow-Origin' header, but the same request works with curl.
Cause: The backend only allows browser origins listed in CORS_ORIGINS. Requests with no Origin header (the mobile app, server-side calls, curl, the pipeline webhook) are not affected, which is why only the browser fails.
Fix:
- Add the exact origin the browser shows in the address bar, with scheme and port and no path or trailing slash, for example
https://dharini.example.org. List every browser app that calls the API, including the Flutter web build (mobile-web) if you serve it.Terminal window CORS_ORIGINS=https://dharini.example.org,https://app.dharini.example.org - Recreate the backend so it reads the change:
docker compose --profile production up -d backend. - Check there is no second
Access-Control-Allow-Originheader added by Nginx. Let the backend handle CORS.
Uploads Fail with 413
Section titled “Uploads Fail with 413”Problem: Uploading images or importing a data file fails with 413 Request Entity Too Large.
Cause: Nginx’s default client_max_body_size is 1 MB. The backend accepts 50 MB request bodies and, for POST /media/upload-images, 25 MB per file.
Fix: raise client_max_body_size in the Nginx server block and reload. See Request Size Limits. A 413 that comes from the backend itself (a JSON body above 50 MB, or a file above 25 MB) has to be reduced on the client side.
Performance Issues
Section titled “Performance Issues”High CPU Usage
Section titled “High CPU Usage”Check resource usage:
docker statsCommon causes:
- Database queries - Check slow query log
- Infinite loops in background jobs
- Memory leaks - Monitor over time
Solutions:
- Scale EC2 instance - Upgrade to larger instance type
- Optimize queries - Add indexes, use EXPLAIN ANALYZE
- Restart services:
Terminal window docker compose --profile production restart
Out of Memory
Section titled “Out of Memory”Problem: Services crash with OOM errors
Check memory:
free -hdocker statsSolutions:
-
Increase EC2 instance memory
-
Add swap space (temporary):
Terminal window sudo fallocate -l 2G /swapfilesudo chmod 600 /swapfilesudo mkswap /swapfilesudo swapon /swapfile -
Reduce Docker memory limits in docker-compose.yml
Slow API Responses
Section titled “Slow API Responses”Debug:
-
Check database performance:
SELECT query, calls, mean_exec_time FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10; -
Monitor RDS metrics in CloudWatch
-
Check Redis connectivity:
Terminal window docker exec -it redis redis-cli ping
Application Issues
Section titled “Application Issues”Login Not Working
Section titled “Login Not Working”Problem: Cannot log in with phone or email OTP
Check:
-
Super admin user exists:
SELECT u.id, u.email, r.name AS roleFROM users uJOIN user_roles ur ON ur."userId" = u.idJOIN roles r ON r.id = ur."roleId"WHERE r.name = 'super-admin'; -
Email OTP:
- Check
SMTP_HOST/SMTP_USER/SMTP_PASS - Check
EMAIL_FROMis verified with your email provider - View backend logs for email send errors
- Check
-
SMS (phone) OTP:
SMS_PROVIDERselects the sender. The default istwilio. Set it toconsoleto send nothing: the backend only logs a masked “would send SMS” line, which is useful in development but means no SMS ever arrives.- With
twilio, checkTWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKENandTWILIO_FROM_NUMBER, and that the Twilio account can send to the destination country. - Look for Twilio errors in
docker logs backend.
-
Testing without a real OTP:
ALLOW_FIXED_OTP=truemakes every OTP123456. Use it only on a test system. It is ignored whenNODE_ENV=production, so it cannot be switched on in production by mistake. -
Too many attempts: the login/OTP routes are rate limited (10 requests per minute per client). Wait a minute and retry.
-
Database connection:
Terminal window docker logs backend | grep -i "database"
File Upload Failing
Section titled “File Upload Failing”Problem: Cannot upload images or files
Check:
-
S3 credentials:
Terminal window # Test from backend containerdocker exec -it backend env | grep S3_ -
S3 bucket permissions:
- Verify IAM user has PutObject permission
- Check bucket policy
-
File limits:
- Nginx:
client_max_body_size(see Uploads Fail with 413) - Application: files are limited to 25 MB each.
MAX_FILE_ALLOW_UPLOADis the maximum number of files per request (default 10), not a size.
- Nginx:
-
Backend logs:
Terminal window docker logs backend | grep -i "upload"
Maps Not Loading
Section titled “Maps Not Loading”Problem: Maps or geospatial features not working
Check:
-
PostGIS installed:
SELECT PostGIS_version(); -
Spatial data:
SELECT id, name, ST_AsText(coordinates) FROM locations WHERE coordinates IS NOT NULL LIMIT 5; -
Frontend map library loaded (check browser console)
GeoLens Layers Empty After Startup
Section titled “GeoLens Layers Empty After Startup”Problem: Right after a first deploy or a fresh database, GeoLens map layers show no data.
Cause: The backend loads the GeoLens reference layers (hundreds of thousands of rows) in the background after it starts listening, so the API is available before the data is. Failures are logged and do not stop the backend.
Fix: wait for it to finish; this can take several minutes on a new database. Follow progress with:
docker logs backend -f | grep -i geolensIf the log shows GeoLens seeding failed, fix the reported error (usually database permissions, a missing PostGIS extension or low disk space) and restart the backend. Seeding does not run when NODE_ENV=development.
Mobile App Maps Are Blank
Section titled “Mobile App Maps Are Blank”Problem: Map screens in the mobile app show grey or empty tiles.
Cause: The default map tiles come from Stadia Maps and need an API key. Without MAP_API_KEY in the mobile app’s .env at build time, tile requests are rejected.
Fix: set MAP_API_KEY in mobile/.env (see mobile/.env.example), rebuild the app, and check the key is valid for your domain or app in the Stadia Maps dashboard.
Sequencing Runs Stuck
Section titled “Sequencing Runs Stuck”Problem: A sequencing run stays in “Submitted” or “Processing” long after the pipeline finished.
How results arrive: The pipeline reports completion by calling the backend webhook, POST /sequencing/pipeline/webhook (public, signed with WEBHOOK_SECRET). As a safety net, a reconciliation job runs every 10 minutes and asks the pipeline about runs that have been active for more than SEQUENCING_RECONCILE_AFTER_S seconds (default 3600, one hour).
Check:
- The webhook is reachable from the pipeline.
PIPELINE_CALLBACK_URLmust be the public URL of that route as the pipeline sees it. Behind the Nginx/api/prefix that ishttps://dharini.example.org/api/sequencing/pipeline/webhook. A localhost or private URL will not work from a pipeline running elsewhere. - Nginx or a firewall is not blocking the pipeline’s requests. Check the Nginx access log for
POST .../sequencing/pipeline/webhookand its status. WEBHOOK_SECRETmatches the secret configured in the pipeline. A mismatch shows as rejected signatures in the backend logs.PIPELINE_URLandPIPELINE_API_KEYare correct, so the backend can submit runs and the reconciliation job can query them.- Wait for reconciliation. If the webhook cannot work (for example in local testing), runs reach their final state only after the reconcile window plus up to 10 minutes. Lower
SEQUENCING_RECONCILE_AFTER_Sto shorten this.
Deployment Issues
Section titled “Deployment Issues”Deployment Files Will Not Download
Section titled “Deployment Files Will Not Download”Problem: curl fails when you fetch docker-compose.yml or deploy.sh, or the file you get is out of date
- Check the URL and branch: the files come from
https://raw.githubusercontent.com/dsih-artpark/dharini/<branch>/, where<branch>isproductionorstaging. - A forked or renamed repository needs its own address in the URL.
- Make sure the downloaded files are not empty (
ls -l docker-compose.yml deploy.sh) and thatdeploy.shis executable (chmod +x deploy.sh).
Image Pull Fails
Section titled “Image Pull Fails”Problem: ./deploy.sh fails while pulling, or the backend or frontend runs old code
The server does not build images; it pulls dharini-backend and dharini-frontend from dsihartpark on Docker Hub (or IMAGE_REGISTRY). They are published when a pull request is merged into staging or production.
- Check the publish run finished in GitHub Actions (“Publish Docker images”) for the commit you expect. Until it does, the tag still points at the previous build.
- Check the tag:
IMAGE_TAGdefaults to the profile name. A leftoverIMAGE_TAGin your shell or.envmakes the server run that tag. - Disk space full:
Terminal window df -hdocker system prune -a - Build from the checkout instead (needs more memory than running the images):
Terminal window ./deploy.sh production --build
Monitoring and Logs
Section titled “Monitoring and Logs”View All Logs
Section titled “View All Logs”# All servicesdocker compose logs -f
# Specific servicedocker logs -f backenddocker logs -f frontend --tail 100
# Since specific timedocker logs backend --since 30mSystem Resources
Section titled “System Resources”# Disk usagedf -hdu -sh /var/lib/docker
# Memoryfree -h
# Docker statsdocker stats
# Processestophtop # if installedNetwork Debugging
Section titled “Network Debugging”# Test port connectivitync -zv your-domain.com 443
# DNS resolutionnslookup your-domain.com
# Trace routetraceroute your-domain.com
# Check listening portssudo netstat -tulpnGetting Help
Section titled “Getting Help”If issues persist:
- Check logs for specific error messages
- Search issues on GitHub: https://github.com/dsih-artpark/dharini/issues
- Create new issue with:
- Error logs
- Environment details (OS, Docker version)
- Steps to reproduce
- Contact the development team
Preventive Measures
Section titled “Preventive Measures”- Regular backups - Database and configuration
- Monitor disk space - Set up alerts
- Update regularly - Pull latest code weekly
- Test in staging before production updates
- Document changes to configuration
- Monitor logs for errors and warnings
Quick Fixes Checklist
Section titled “Quick Fixes Checklist”When something breaks, try these in order:
- ☐ Check Docker containers:
docker ps - ☐ View logs:
docker logs backend - ☐ Restart services:
docker compose restart - ☐ Check .env file variables
- ☐ Verify database connection
- ☐ Test Nginx config:
sudo nginx -t - ☐ Check security groups (AWS)
- ☐ Review recent changes:
git log - ☐ Pull and redeploy if needed:
./deploy.sh production - ☐ Check disk space:
df -h