A minimal proof of concept demonstrating mDNS/Bonjour service discovery between a Django server and Flutter applications on iOS and macOS.
- Django Server: Advertises itself using Zeroconf (python-zeroconf library) with TTL management and proper cleanup
- Flutter iOS/macOS App: Discovers the Django server using Bonsoir package (native Bonjour/mDNS)
- Automatic mDNS service registration with customizable TTL values
- Proper service cleanup on shutdown (goodbye packets)
- Cross-platform Flutter app (iOS and macOS)
- Minimal dependencies and clean architecture
- Ghost service cleanup utilities
- Python 3.8+
- Django==5.0.6
- zeroconf==0.147.2
- Flutter SDK: >=3.0.0 <4.0.0
- bonsoir: ^5.1.11 (cross-platform mDNS, uses native iOS Bonjour)
- http: ^1.2.0
- iOS 13.0+ (required by bonsoir_darwin)
cd django-server
python3 -m venv venv
source venv/bin/activate # On macOS/Linux
pip install -r requirements.txt
python manage.py migrate
python manage.py runserver 0.0.0.0:8000
You should see:
✅ mDNS service registered: Django API Server on [IP]:8000
Service type: _django-api._tcp.local.
cd flutter-app
flutter pub get
# For iOS Simulator
flutter run -d "iPhone"
# For physical iOS device
flutter run -d "Your Device Name"
# For macOS
flutter run -d macos
- Django server starts and automatically registers mDNS service with 120-second TTL
- Service advertises on
_django-api._tcp.local.
with properties - Flutter app discovers services when "Start Discovery" is tapped
- App resolves service details and displays host:port
- "Test" button sends HTTP request to verify connectivity
GET /api/
- Returns server information and current time
The Django server sets explicit TTL values:
- Host TTL: 120 seconds (A/AAAA records)
- Other TTL: 120 seconds (SRV/TXT records)
Services expire automatically if not refreshed within the TTL period.
Info.plist (ios/Runner/Info.plist
):
<key>NSBonjourServices</key>
<array>
<string>_django-api._tcp</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>This app needs local network access to discover Django servers</string>
Minimum iOS Version: 13.0 (set in ios/Podfile
)
Code Signing: Automatic signing enabled in Xcode project
Entitlements (macos/Runner/*.entitlements
):
<key>com.apple.security.network.client</key>
<true/>
Plus same Info.plist entries as iOS.
If you see lingering "ghost" services after server shutdown:
cd django-server
source venv/bin/activate
python cleanup_mdns.py
This sends goodbye packets for all Django API Server instances.
cd django-server
source venv/bin/activate
python test_ttl.py
Creates a test service with 30-second TTL to verify expiration.
Provisioning Profile Error:
# Add automatic provisioning to Xcode project
open flutter-app/ios/Runner.xcworkspace
# Select team in Signing & Capabilities
Minimum iOS Version Error:
- Already fixed: Podfile sets
platform :ios, '13.0'
Services Not Found:
- Verify same network:
ping [server-ip]
- Check mDNS:
dns-sd -B _django-api._tcp
- Verify firewall allows port 5353 (mDNS) and 8000 (HTTP)
Ghost Services:
- Run
cleanup_mdns.py
to send goodbye packets - Services expire after 2 minutes (120s TTL)
Multiple Service Instances:
- Kill all Django processes:
pkill -f "manage.py runserver"
- Run cleanup script to remove registrations
Django Must Bind to All Interfaces:
# Correct - accessible from network
python manage.py runserver 0.0.0.0:8000
# Wrong - only localhost
python manage.py runserver # defaults to 127.0.0.1
The Django server implements proper cleanup:
- Signal handlers for SIGTERM and SIGINT
- atexit registration for cleanup
- Sends goodbye packets (TTL=0) on shutdown
Server detects actual network IP (not localhost):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
local_ip = s.getsockname()[0]
Flutter app performs two-step discovery:
- Find service via browse
- Resolve details before use
mdns-poc/
├── django-server/
│ ├── api/
│ │ ├── apps.py # mDNS registration with TTL
│ │ └── views.py # Minimal API endpoint
│ ├── cleanup_mdns.py # Ghost service cleanup
│ ├── test_ttl.py # TTL testing utility
│ └── requirements.txt
├── flutter-app/
│ ├── lib/
│ │ └── main.dart # Minimal discovery UI
│ ├── ios/
│ │ ├── Runner/Info.plist
│ │ └── Podfile # iOS 13.0 minimum
│ └── pubspec.yaml
└── README.md
- Added explicit TTL configuration (120 seconds)
- Implemented proper service cleanup with goodbye packets
- Added cleanup utilities for ghost services
- Fixed iOS automatic code signing
- Set minimum iOS version to 13.0 for bonsoir compatibility
- Minimized codebase (~54% reduction)