Database Connection Consistency Fixes
Problem Statement
The codebase had 4 different places where database connections were configured, each with inconsistent behavior:
| File | Line | Original Behavior | Issue |
|---|---|---|---|
app.py |
163 | Auto-built from POSTGRES_* with optional password | ⚠️ Document expectation to set password |
configure.py |
26 | REQUIRED DATABASE_URL to be set | ❌ Too strict, conflicted with app.py |
poller/cappoller.py |
1614 | Auto-built from POSTGRES* with optional password | ✅ Good, but docstring showed old defaults |
app_core/models.py |
24 | Just checked if URL exists | ✅ Fine (no changes needed) |
Issues Found
1. configure.py Required DATABASE_URL (Too Strict)
Problem:configure.py OLD CODE
SQLALCHEMYDATABASEURI = os.environ.get('DATABASE_URL')
if not SQLALCHEMYDATABASEURI:
raise ValueError("DATABASE_URL environment variable must be set...")
This failed because app.py auto-builds DATABASEURL from POSTGRES* variables, but configure.py required it to be explicitly set. This created a conflict where the app would fail to start.
configure.py to match app.py's behavior - auto-build from POSTGRES* or accept DATABASEURL.2. app.py Allowed Empty Passwords (Security Risk)
Problem:app.py OLD CODE
password = os.getenv('POSTGRES_PASSWORD', '')
user_part = quote(user, safe='')
if password:
authpart = f"{userpart}:{quote(password, safe='')}"
else:
authpart = userpart # ← Allowed no password!
This allowed connecting to the database without a password, which is a security risk.
Solution: Added password requirement check (consistent with cap_poller.py):if not password:
raise ValueError("POSTGRES_PASSWORD environment variable must be set...")
Compatibility Update:
Later feedback required tolerating deployments without POSTGRES_PASSWORD while still
escaping credentials. The helper now falls back to a password-less connection string when
no password is supplied but continues to URL-encode credentials whenever they are
provided.3. cap_poller.py Showed Old Default Credentials in Docs
Problem:Docstring showed:
"""
Defaults for Docker (override with env or --database-url):
POSTGRES_USER=casaos # ← OLD DEFAULTS
POSTGRES_PASSWORD=casaos # ← SECURITY ISSUE - showed in docs!
DATABASE_URL=postgresql+psycopg2://casaos:casaos@...
"""
This was misleading because:
- Those defaults were removed from the code (already fixed earlier)
- Showing passwords in documentation is bad practice
- Implied these were still valid defaults
"""
Database Configuration (via environment variables or --database-url):
POSTGRES_HOST - Database host (default: host.docker.internal; override for Docker services)
POSTGRES_PORT - Database port (default: 5432)
POSTGRESDB - Database name (defaults to POSTGRESUSER)
POSTGRES_USER - Database user (default: postgres)
POSTGRES_PASSWORD - Database password (optional, recommended) All database credentials should be explicitly configured via environment variables when available.
No default passwords are provided for security.
"""
What Was Fixed
File: app.py
Changes:
- Added URL-encoding for credentials to handle special characters
- Added comprehensive docstring explaining behavior
- Improved code comments and optional password fallback messaging
- Consistent with configure.py and cap_poller.py
password = os.getenv('POSTGRES_PASSWORD', '')
... allowed empty password
After:
password = os.getenv('POSTGRES_PASSWORD', '')
user_part = quote(user, safe='')
password_part = quote(password, safe='') if password else ''if password_part:
authsegment = f"{userpart}:{password_part}"
else:
authsegment = userpart
url = f"postgresql+psycopg2://{auth_segment}@{host}:{port}/{database}"
File: configure.py
Changes:
- Auto-builds DATABASEURL from POSTGRES* (lines 28-47)
- CRITICAL FIX: Added URL-encoding of credentials to handle special characters
- Matches app.py's behavior exactly
- No longer requires DATABASE_URL to be pre-set and tolerates missing password values
SQLALCHEMYDATABASEURI = os.environ.get('DATABASE_URL')
if not SQLALCHEMYDATABASEURI:
raise ValueError("DATABASE_URL environment variable must be set...")
After:
from urllib.parse import quoteSQLALCHEMYDATABASEURI = os.environ.get('DATABASE_URL')
if not SQLALCHEMYDATABASEURI:
# Build from individual POSTGRES_* variables (same logic as app.py)
user = os.environ.get('POSTGRES_USER', 'postgres') or 'postgres'
password = os.environ.get('POSTGRES_PASSWORD', '')
host = os.environ.get('POSTGRES_HOST', 'postgres') or 'postgres'
port = os.environ.get('POSTGRES_PORT', '5432') or '5432'
database = os.environ.get('POSTGRES_DB', user) or user
# URL-encode credentials to handle special characters (@, :, /, etc.)
user_part = quote(user, safe='')
password_part = quote(password, safe='') if password else ''
if password_part:
authsegment = f"{userpart}:{password_part}"
else:
authsegment = userpart
SQLALCHEMYDATABASEURI = f"postgresql+psycopg2://{auth_segment}@{host}:{port}/{database}"
Security Note: Without URL encoding, passwords containing @, :, /, #, ?, or other reserved URI characters would create invalid connection strings and break the application. This was a critical bug that would prevent apps from starting with strong passwords.File: poller/cap_poller.py
Changes:
- Rewrote docstring to remove old credentials (lines 7-16)
- Documented configuration without showing passwords
- Clarified that credentials are recommended and optional when absent
"""
Defaults for Docker (override with env or --database-url):
POSTGRES_USER=casaos
POSTGRES_PASSWORD=casaos
DATABASE_URL=postgresql+psycopg2://casaos:casaos@postgresql:5432/casaos
"""
After:
"""
Database Configuration (via environment variables or --database-url):
POSTGRES_HOST - Database host (default: host.docker.internal; override for Docker services)
POSTGRES_PORT - Database port (default: 5432)
POSTGRESDB - Database name (defaults to POSTGRESUSER)
POSTGRES_USER - Database user (default: postgres)
POSTGRES_PASSWORD - Database password (optional, recommended)
DATABASE_URL - Or provide full connection string to override individual vars All database credentials should be explicitly configured via environment variables when available.
No default passwords are provided for security.
"""
Unified Behavior (All Files Now Consistent)
All three files now follow the same logic:
Priority Order:
- DATABASE_URL (if set) - Use it directly
- POSTGRES_* variables - Build URL from components
- POSTGRES_HOST (default: 'host.docker.internal'; override to 'alerts-db' when using the embedded profile)
- POSTGRES_PORT (default: '5432')
- POSTGRESDB (default: POSTGRESUSER)
- POSTGRES_USER (default: 'postgres')
- POSTGRES_PASSWORD (optional fallback builds without credentials)
Security Requirements:
✅ POSTGRES_PASSWORD is strongly recommended ✅ No default passwords ✅ Clear error messages if missing ✅ URL-encoding for special characters in credentialsTesting
After these changes, the application will:
✅ Work with DATABASE_URL:
DATABASE_URL=postgresql+psycopg2://user:pass@host:5432/db
All three files will use this
✅ Work with POSTGRES_* variables:
POSTGRES_HOST=alerts-db
POSTGRES_PORT=5432
POSTGRES_DB=alerts
POSTGRES_USER=postgres
POSTGRES_PASSWORD=mypassword
All three files will auto-build: postgresql+psycopg2://postgres:mypassword@alerts-db:5432/alerts
✅ Fallback when password is omitted:
POSTGRES_USER=postgres
POSTGRES_HOST=alerts-db
Missing POSTGRES_PASSWORD
All three files will auto-build: postgresql+psycopg2://postgres@alerts-db:5432/postgres
Verification Commands
Test that app starts with proper credentials
cd ~/eas-station
sudo docker compose restart appCheck logs for errors
sudo docker compose logs app | grep -i "error\|password"Should see no errors about DATABASE_URL or missing credentials
Summary
| Aspect | Before | After |
|---|---|---|
| Number of inconsistencies | 4 different behaviors | 1 unified behavior |
| Password requirement | Optional in app.py | Optional with encoded fallback |
| DATABASE_URL flexibility | Required in configure.py | Optional in all files |
| Documentation | Showed old credentials | Security-conscious docs |
| Error messages | Inconsistent | Clear and consistent |
| ALERTSDB* usage | Still referenced in docs | Completely removed |
Benefits
✅ Consistent: All files use the same logic ✅ Secure: Passwords strongly encouraged and URL-encoded when provided ✅ Flexible: Supports both DATABASEURL and POSTGRES* vars ✅ Clear: Better error messages and documentation ✅ Maintainable: Single source of truth for connection logic
Migration Impact
No breaking changes for properly configured environments!If your .env file has either:
DATABASE_URLset, OR- POSTGRES_* variables (password optional)
Then your application will continue to work without any changes.
Environments that omit POSTGRES_PASSWORD now connect using a password-less URI. For
security, production deployments should still provide a password.
This document is served from docs/troubleshooting/DATABASE_CONSISTENCY_FIXES.md in the EAS Station installation.