| layout | default |
|---|---|
| title | Quick Start |
| nav_order | 2 |
{: .no_toc }
Get iCAD Dispatch v2 running in 15 minutes. {: .fs-6 .fw-300 }
{: .no_toc .text-delta }
- TOC {:toc}
You need:
- A Linux server (Ubuntu 22.04+ recommended) with a public IP address
- Docker and Docker Compose installed
- A domain name pointing to your server (two subdomains recommended):
dispatch.yourdomain.com— for the admin dashboardmap.yourdomain.com— for the public live map
- Root or sudo access to the server
- Ability to copy/paste commands into a terminal
docker --version
docker compose versionIf you see version numbers, you're good. If you see "command not found", install Docker:
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in for the group change to take effectThis downloads the app code from GitHub to your server.
cd ~
git clone https://github.com/YOUR_GITHUB_USERNAME/icad_dispatch_v2.git
cd icad_dispatch_v2What this does: Creates a folder called icad_dispatch_v2 with all the code, configs, and documentation.
iCAD Dispatch needs a file called .env that contains all your passwords, URLs, and settings. We provide a template called .env.example.
cp .env.example .env
nano .envWhat this does: Copies the template to a new file called .env, then opens it in the nano text editor.
These 6 values are required. The app will not start without them.
| Variable | What it is | Example |
|---|---|---|
BASE_URL |
Your dashboard URL | https://dispatch.yourdomain.com |
TIMEZONE |
Your timezone (find yours here) | America/New_York |
PG_PASSWORD |
Database password | MyVerySecretPassword123! |
PUBLIC_MAP_API_KEY |
Shared secret between main app and public map | Generate with openssl rand -hex 24 |
MAP_SECRET_KEY |
Secret for public map sessions | Generate with openssl rand -hex 32 |
ROOT_PASSWORD |
Your admin login password | MyAdminPassword456! |
Run these commands on your server and copy the output into .env:
# For PUBLIC_MAP_API_KEY (48 characters)
openssl rand -hex 24
# For MAP_SECRET_KEY (64 characters)
openssl rand -hex 32This is a shared password between two containers:
- The main app (
icad_dispatch) uses it to authenticate when pushing calls to the public map - The public map (
public_map) uses it to verify those calls are legitimate
Both containers must have the exact same value. If they don't match, the public map won't show new calls.
- Press
Ctrl + O(that's the letter O, not zero) to save - Press
Enterto confirm the filename - Press
Ctrl + Xto exit
This single command downloads, builds, and starts all three containers:
docker compose -f docker-compose.production.yml up -dWhat this does:
- Downloads PostgreSQL 16 + PostGIS (the database)
- Builds the main app container from the code
- Builds the public map container from the code
- Starts all three containers in the background (
-dmeans "detached")
How long it takes:
- First run: 5–10 minutes (downloads and builds everything)
- After that: 30 seconds
docker compose -f docker-compose.production.yml psExpected output:
NAME STATUS PORTS
icad_dispatch_v2-postgres-1 Up (healthy) 5432/tcp
icad_dispatch_v2-icad_dispatch-1 Up (healthy) 0.0.0.0:9911->9911/tcp
icad_dispatch_v2-public_map-1 Up 0.0.0.0:5000->5000/tcp
What each container does:
postgres— Stores all call data, transcripts, and settingsicad_dispatch— The main app you log into (dashboard + API)public_map— The public live map citizens view
Check the logs to see what went wrong:
# Main app logs
docker compose -f docker-compose.production.yml logs -f icad_dispatch
# Public map logs
docker compose -f docker-compose.production.yml logs -f public_map
# Database logs
docker compose -f docker-compose.production.yml logs -f postgresCommon fix: If the main app says "Database connection refused", the database wasn't ready yet. Just wait 30 seconds and run docker compose -f docker-compose.production.yml restart icad_dispatch.
You cannot use HTTP in production. Browsers block audio playback and WebSocket connections over HTTP. You must use HTTPS.
You need a reverse proxy — a program that sits in front of your containers and handles HTTPS for you.
Caddy is the easiest option because it automatically gets HTTPS certificates from Let's Encrypt.
sudo apt-get install caddy
sudo nano /etc/caddy/CaddyfilePaste this (replace yourdomain.com with your actual domain):
dispatch.yourdomain.com {
reverse_proxy localhost:9911
}
map.yourdomain.com {
reverse_proxy localhost:5000
}
Save and reload:
sudo systemctl reload caddyWhat this does:
- When someone visits
https://dispatch.yourdomain.com, Caddy forwards them to the main app on port 9911 - When someone visits
https://map.yourdomain.com, Caddy forwards them to the public map on port 5000 - Caddy automatically gets and renews HTTPS certificates
If you already use nginx or need more control:
sudo apt-get install nginx
sudo nano /etc/nginx/sites-available/icadPaste this:
server {
listen 80;
server_name dispatch.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name dispatch.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:9911;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 80;
server_name map.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name map.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Enable:
sudo ln -s /etc/nginx/sites-available/icad /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxIf you don't have SSL certificates yet, use Let's Encrypt with Certbot:
sudo apt-get install certbot python3-certbot-nginx
sudo certbot --nginx -d dispatch.yourdomain.com -d map.yourdomain.com- Open
https://dispatch.yourdomain.comin your browser - Login with:
- Username:
root - Password: The
ROOT_PASSWORDfrom your.envfile
- Username:
- Immediately change the password via the user menu (top right)
Now that you're logged in, you need to tell iCAD Dispatch about your radio system.
- Go to Dashboard → Systems
- Click Add System
- Fill in:
- System Name: e.g., "County Fire" or "EMS Dispatch"
- Decimal ID: Your radio system ID (ask your radio tech if unsure)
- Timezone: Select from the dropdown
- Click Save
- Copy the API Key shown on the system card — you'll need this to upload calls
- Click Edit on your new system
- Go to the Address Extraction tab
- Check Enable Address Extraction
- Under Geocoding Regions, add your county and state
- Example: County =
Renfrew, State =ON, Country =CA
- Example: County =
- Click Save
What this does: When a call is uploaded, iCAD will try to find addresses in the transcript and look them up on a map.
- Click Edit on your system
- Go to the Notifiers tab
- Configure the channels you want:
Each notifier has a Test button. Click it to send a test message and verify it works.
- Go to Dashboard → Systems → Your System
- Scroll to the notifier section
- Click Test on any notifier (Discord, Telegram, etc.)
- Check that the test call appears on
https://map.yourdomain.com
curl -X POST https://dispatch.yourdomain.com/api/call-upload \
-H "X-API-Key: YOUR_SYSTEM_API_KEY" \
-F "audio=@/path/to/test.mp3" \
-F "talkgroup=410837" \
-F "system_id=1"Replace:
YOUR_SYSTEM_API_KEY— the key you copied in Step 7/path/to/test.mp3— path to an audio file on your server410837— your talkgroup ID1— your system ID (the number shown in the dashboard)
Before going live, secure your server:
sudo ufw default deny incoming
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw enableWhat this does: Blocks all incoming connections except SSH (for you to log in), HTTP, and HTTPS.
The containers expose ports 9911 and 5000 on 0.0.0.0 by default. Only your reverse proxy (Caddy/nginx) should access these.
If you have a firewall, block ports 9911 and 5000 from the internet:
sudo ufw deny 9911
sudo ufw deny 5000
sudo ufw deny 5432See Security Hardening for:
- fail2ban setup
- Automatic security updates
- Database backups
- More hardening steps
# View all running containers
docker compose -f docker-compose.production.yml ps
# View logs (press Ctrl+C to stop watching)
docker compose -f docker-compose.production.yml logs -f icad_dispatch
docker compose -f docker-compose.production.yml logs -f public_map
docker compose -f docker-compose.production.yml logs -f postgres
# Restart a specific container
docker compose -f docker-compose.production.yml restart icad_dispatch
docker compose -f docker-compose.production.yml restart public_map
# Stop everything (keeps data)
docker compose -f docker-compose.production.yml down
# Start everything again
docker compose -f docker-compose.production.yml up -d
# Update to latest code
git pull origin main
docker compose -f docker-compose.production.yml build
docker compose -f docker-compose.production.yml up -d
# Backup database
docker exec icad_dispatch_v2-postgres-1 pg_dump -U icad icad_dispatch > backup.sql
# Restore database
cat backup.sql | docker exec -i icad_dispatch_v2-postgres-1 psql -U icad -d icad_dispatch- Security Hardening — Essential production checklist
- Geocoding Setup — Fine-tune address lookup
- Public Map — Customize the live map
- API Reference — Integrate with external systems
- Troubleshooting — Fix common issues
Prefer automation? Use the one-click installer:
curl -fsSL https://raw.githubusercontent.com/YOUR_GITHUB_USERNAME/icad_dispatch_v2/main/install.sh | bash