Healthcare Data Bridge: Securely connect on-premises radiology and clinical systems to the cloud
RadBridge is a containerized platform for bridging radiology imaging (DICOM) and clinical data (HL7/FHIR) from on-premises healthcare systems to cloud destinations. Features Orthanc DICOM server, Mirth Connect integration engine, and PostgreSQL—orchestrated with Podman Quadlet and systemd for enterprise-grade security and reliability.
Status: Core infrastructure is production-ready. Healthcare integration layer (Orthanc-Mirth communication) is under active development. See Project Status for details.
- Overview
- Architecture
- Key Features
- Project Status
- Project Structure
- Quick Start
- Services
- Network & Ports
- Deployment
- Security
- Resource Management
- Monitoring & Logging
- Documentation
- Contributing
- License
RadBridge provides a complete on-premises healthcare data bridge designed for:
- Cloud Migration: Securely stream radiology and clinical data from on-prem to cloud
- Healthcare Interoperability: HL7, FHIR, and DICOM protocol support
- Enterprise Security: Rootless containers, capability dropping, secrets management, HIPAA-ready
- Production Reliability: systemd integration, resource limits, automated restarts
- Operational Excellence: Centralized logging, health checks, reverse proxy architecture
Perfect for healthcare organizations migrating medical imaging and clinical data to the cloud while maintaining on-premises PACS and EMR integration.
flowchart TB
subgraph external[External Access]
HTTPS[HTTPS :8443]
HTTP[HTTP :8080]
DICOM[DICOM :4242]
end
subgraph proxy[Reverse Proxy Layer]
NGINX[Nginx<br/>Alpine 1.27]
end
subgraph apps[Application Services]
ORTHANC[Orthanc DICOM Server<br/>26.1.0]
MIRTH[Mirth Connect<br/>Integration Engine 4.5.0]
PGADMIN[pgAdmin 4<br/>Database UI]
end
subgraph data[Data Layer]
POSTGRES[(PostgreSQL 17<br/>Database)]
VOLUMEORTHANC[(orthanc_data<br/>Volume)]
VOLUMEPOSTGRES[(postgres_data<br/>Volume)]
VOLUMEMIRTH[(mirth_data<br/>Volume)]
end
HTTP --> NGINX
NGINX -.->|/orthanc/| ORTHANC
NGINX -.->|/pgadmin/| PGADMIN
HTTPS --> MIRTH
DICOM --> ORTHANC
MIRTH --> POSTGRES
PGADMIN --> POSTGRES
ORTHANC --> VOLUMEORTHANC
ORTHANC --> POSTGRES
POSTGRES --> VOLUMEPOSTGRES
MIRTH --> VOLUMEMIRTH
style HTTP fill:#e1f5ff
style HTTPS fill:#e1f5ff
style DICOM fill:#e1f5ff
style NGINX fill:#90EE90
style POSTGRES fill:#4169E1,color:#fff
style ORTHANC fill:#FF6B6B,color:#fff
style MIRTH fill:#FFA500,color:#fff
- HL7/FHIR Processing: Mirth Connect 4.5.0 for healthcare message transformation
- DICOM Server: Orthanc 26.1.0 for medical imaging storage and PACS integration
- Database Backend: PostgreSQL 17 with separate databases per service
- Rootless Containers: Full user namespace isolation
- Secrets Management: Podman secrets for credential injection (zero plaintext passwords)
- Capability Dropping: Minimal Linux capabilities per service
- Network Isolation: Internal network with reverse proxy access control
- Audit Logging: Comprehensive systemd journal integration
- systemd Integration: Native service management with Quadlet
- Resource Limits: CPU, memory, and task quotas prevent resource exhaustion
- Health Monitoring: Built-in health checks and restart policies
- Zero-Downtime Updates: Declarative configuration management
- Centralized Logging: Structured logs with unique identifiers per service
- Reverse Proxy: Nginx-based unified access point for web interfaces
- Database Management: pgAdmin web interface for administration
- Volume Management: Persistent storage for data files and images
Current Status: Infrastructure Foundation Complete - Integration Layer In Progress
RadBridge has a fully functional container orchestration platform with production-grade security hardening. The foundational services (PostgreSQL, Orthanc DICOM server, Mirth Connect, reverse proxy) are deployed and operational.
- Rootless Podman Quadlet container orchestration
- PostgreSQL database backend with separate databases per service
- Orthanc DICOM server with C-STORE/C-FIND/C-MOVE protocol support
- Mirth Connect integration engine deployment
- Nginx reverse proxy with unified web access
- Podman secrets management for credentials
- Linux capability dropping across all containers -SystemD resource limits and restart policies
- Structured audit logging with unique identifiers
- Automated setup and installation scripts
Orthanc-Mirth Integration Pipeline
The core infrastructure is complete, but the integration layer connecting Orthanc to Mirth is under active development:
- Orthanc Lua Webhook: Lua script to trigger HTTP notifications when DICOM studies become stable
- Mirth HTTP Listener Channel: Corresponding channel to receive Orthanc webhooks and process study metadata
- DICOM Study Routing: Logic to route studies to appropriate cloud destinations
- HL7/FHIR Transformation: Convert DICOM metadata to HL7v2 or FHIR format for downstream systems
- Error Handling: Retry logic, dead letter queues, and failure notification
Near Term (Q1 2026)
- Complete Orthanc Lua webhook implementation
- Build Mirth channel for study-level notifications
- Document integration configuration
- Add sample DICOM test data and workflows
Medium Term (Q2 2026)
- TLS/SSL termination at nginx reverse proxy
- Cloud destination connectors (AWS S3, Azure Blob, GCP Storage)
- FHIR ImagingStudy resource generation
- Prometheus/Grafana monitoring dashboards
Long Term (2026+)
- Multi-site deployment capabilities
- Advanced anonymization and de-identification
- DICOM modality worklist (MWL) support
- HL7v2 ADT feed integration
- Kubernetes deployment options
Important: This is an active development project. The container infrastructure is production-ready, but the healthcare integration workflows are still being built. Contributions are welcome!
radbridge/
├── containers/ # Podman Quadlet container definitions
│ ├── postgres.container
│ ├── orthanc.container
│ ├── mirth.container
│ ├── pgadmin.container
│ ├── nginx.container
│ └── edge.network # Network configuration
├── config/ # Service configuration files
│ ├── nginx.conf # Reverse proxy configuration
│ └── init-databases.sh # Database initialization script
├── scripts/ # Automation scripts
│ ├── install.sh # Install files to systemd directory
│ └── setup.sh # Automated setup and deployment
├── docs/ # Documentation
│ ├── CONTRIBUTING.md
│ ├── SECURITY.md
│ └── CHANGELOG.md
├── .github/ # GitHub templates and workflows
│ ├── ISSUE_TEMPLATE/
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── copilot-instructions.md
├── .gitignore
├── Makefile # Command shortcuts
├── README.md # This file
├── LICENSE # MIT License
└── CREDENTIALS.md # Generated by setup (not in git)
The install.sh script copies files from this repository to ~/.config/containers/systemd/ where Podman Quadlet expects to find them:
containers/*.container→ Container service definitionscontainers/edge.network→ Network configurationconfig/nginx.conf→ Mounted by nginx containerconfig/init-databases.sh→ Mounted by postgres container
- Linux with systemd support (tested on Ubuntu 24.04 in WSL)
- Podman 4.0+ installed
- User systemd services enabled:
loginctl enable-linger $USER
-
Clone the repository
git clone https://github.com/csphu/radbridge.git cd radbridge -
Install Quadlet files to systemd user directory
./scripts/install.sh
This script copies container definitions from
containers/and configuration files fromconfig/to~/.config/containers/systemd/where systemd can find them. -
Run automated setup (recommended)
./scripts/setup.sh
The setup script will:
- Generate secure random passwords
- Create Podman secrets
- Start all services in correct order
- Save credentials to
CREDENTIALS.md
Or configure manually:
# Generate and store secure passwords POSTGRES_PWD=$(openssl rand -base64 16) ORTHANC_DB_PWD=$(openssl rand -base64 16) MIRTH_DB_PWD=$(openssl rand -base64 16) ORTHANC_ADMIN_PWD=$(openssl rand -base64 16) PGADMIN_PWD=$(openssl rand -base64 16) # Create Podman secrets echo -n "$POSTGRES_PWD" | podman secret create postgres_password - echo -n "$ORTHANC_DB_PWD" | podman secret create orthanc_db_password - echo -n "$MIRTH_DB_PWD" | podman secret create mirth_db_password - echo -n "$PGADMIN_PWD" | podman secret create pgadmin_password - echo -n "{\"admin\": \"$ORTHANC_ADMIN_PWD\"}" | podman secret create orthanc_registered_users - # Save credentials echo "PostgreSQL root: $POSTGRES_PWD" > CREDENTIALS.md echo "Orthanc DB user: $ORTHANC_DB_PWD" >> CREDENTIALS.md echo "Mirth DB user: $MIRTH_DB_PWD" >> CREDENTIALS.md echo "Orthanc admin: $ORTHANC_ADMIN_PWD" >> CREDENTIALS.md echo "pgAdmin: $PGADMIN_PWD" >> CREDENTIALS.md # Start services manually systemctl --user daemon-reload systemctl --user start postgres.service pgadmin.service orthanc.service mirth.service nginx.service
-
Verify deployment
systemctl --user status postgres orthanc nginx journalctl --user -t orthanc-quadlet -n 20
-
Access services
- Orthanc Web UI: http://localhost:8080/orthanc/ (user:
admin) - pgAdmin: http://localhost:8080/pgadmin/ (email:
admin@example.com) - Mirth Connect: https://localhost:8443
See CREDENTIALS.md for passwords.
- Orthanc Web UI: http://localhost:8080/orthanc/ (user:
Purpose: Unified access point for web management interfaces
Configuration: nginx.conf (mounted read-only)
Image: nginx:1.27-alpine
Resources: CPU 100%, Memory 256MB, Tasks 64
Routing:
/orthanc/→ Orthanc Web UI (port 8042)/pgadmin/→ pgAdmin UI (port 80)/health→ Health check endpoint
Benefits: Network isolation, centralized logging, TLS termination ready
Purpose: Relational database backend for all services
Image: postgres:17-alpine
Databases: orthancdb (Orthanc), mirthdb (Mirth)
Users: orthancuser, mirthuser (isolated credentials)
Init Script: init-databases.sh
Resources: CPU 200%, Memory 2GB, Tasks 512
Purpose: Healthcare integration engine for HL7, FHIR message processing
Image: nextgenhealthcare/connect:4.5.0
Database: PostgreSQL (mirthdb)
Access: https://localhost:8443
Resources: CPU 150%, Memory 1GB, Tasks 256
Purpose: DICOM medical imaging server with PACS integration
Image: orthancteam/orthanc:26.1.0
Database: PostgreSQL (orthancdb) for metadata indexing
Storage: Volume orthanc_data for DICOM files
Protocols:
- DICOM C-STORE/C-FIND/C-MOVE (port 4242)
- Web UI (via nginx reverse proxy)
Resources: CPU 200%, Memory 2GB, Tasks 512
Purpose: Web-based PostgreSQL administration interface
Image: dpage/pgadmin4:latest
Access: http://localhost:8080/pgadmin/
Email: admin@example.com
Resources: CPU 100%, Memory 512MB, Tasks 128
All services communicate over the systemd-edge bridge network defined in edge.network. This provides:
- Internal DNS: Services resolve each other by container name
- Isolation: No host network access by default
- Security: Only explicitly published ports accessible externally
| Port | Service | Purpose | Public |
|---|---|---|---|
| 8080 | Nginx | HTTP reverse proxy (Orthanc, pgAdmin) | Yes |
| 8443 | Mirth Connect | HTTPS web UI and API | Yes |
| 4242 | Orthanc | DICOM protocol (C-STORE, C-FIND, C-MOVE) | Yes |
| 5432 | PostgreSQL | Database connections | No - Internal only |
Network Isolation Benefits:
- Web management interfaces not directly exposed
- Single point of access control
- Centralized logging and monitoring
- Foundation for TLS/SSL termination
For convenience, common operations are available via make:
make help # Show all available commands
make setup # Run initial setup
make start # Start all services
make stop # Stop all services
make status # Check service status
make logs # View recent logs
make health # Run health checksSee Makefile for all available commands.
Start all services:
systemctl --user daemon-reload
systemctl --user start postgres.service pgadmin.service mirth.service orthanc.service nginx.serviceCheck status:
systemctl --user status postgres orthanc mirthEnable auto-start on boot:
systemctl --user enable postgres.service orthanc.service nginx.serviceStop services:
systemctl --user stop postgres orthanc mirth pgadmin nginxView logs:
journalctl --user -t orthanc-quadlet -n 50 --no-pager
journalctl --user -t postgres-quadlet -fList volumes:
podman volume lsInspect volume:
podman volume inspect orthanc_dataBackup volume:
podman volume export orthanc_data -o orthanc_backup_$(date +%Y%m%d).tarRestore volume:
podman volume import orthanc_data orthanc_backup_20260212.tarFor comprehensive security information, see docs/SECURITY.md including:
- HIPAA compliance considerations
- Vulnerability reporting procedures
- Security best practices and hardening checklist
- Incident response guidelines
This deployment uses Podman secrets to securely manage passwords and sensitive data. Secrets are stored outside container configuration and injected as environment variables at runtime.
Secret reference:
| Secret Name | Used By | Purpose |
|---|---|---|
postgres_password |
postgres | PostgreSQL root password |
orthanc_db_password |
postgres, orthanc | Orthanc database user password |
mirth_db_password |
postgres, mirth | Mirth database user password |
orthanc_registered_users |
orthanc | Orthanc web UI credentials (JSON) |
pgadmin_password |
pgadmin | pgAdmin web UI password |
Managing secrets:
# List secrets
podman secret ls
# View secret metadata (not the value)
podman secret inspect postgres_password
# Rotate a secret
systemctl --user stop orthanc.service
podman secret rm orthanc_registered_users
echo -n '{"admin": "newpassword"}' | podman secret create orthanc_registered_users -
systemctl --user start orthanc.service
# Remove a secret
podman secret rm secret_nameHow it works: Secrets are referenced in .container files using:
Secret=secret_name,type=env,target=ENVIRONMENT_VARIABLE
Podman injects secret values as environment variables at container startup, ensuring no plaintext passwords in configuration files or logs.
All containers implement defense-in-depth security measures:
Containers drop all unnecessary capabilities to minimize attack surface:
PostgreSQL & Mirth:
DropCapability=ALL
AddCapability=CHOWN CAP_DAC_OVERRIDE FOWNER SETGID SETUID
Orthanc & pgAdmin (require network binding):
DropCapability=ALL
AddCapability=CHOWN CAP_DAC_OVERRIDE FOWNER SETGID SETUID NET_BIND_SERVICE
Verify capabilities:
podman inspect orthanc --format '{{.EffectiveCaps}}'All containers include:
- NoNewPrivileges=true: Prevents privilege escalation
- SecurityLabelDisable=true: Disables SELinux/AppArmor for performance
- Rootless mode: User namespace isolation (UID 0 in container → UID 1000 on host)
Rootless Podman automatically isolates container UIDs:
- Container root (UID 0) → Host user (UID 1000)
- Container users (UID 1-65536) → Host UIDs 100000-165535
Verify mapping:
podman unshare cat /proc/self/uid_map
# Output: 0 1000 1 (container root = host user)
# 1 100000 65536 (container users = unprivileged)Production deployment checklist:
- Secrets Management: Podman secrets instead of plaintext passwords
- TLS/SSL Encryption: Enable HTTPS for all web interfaces
- Remove Git Credential History: Clean committed passwords from git history
- Network Isolation: Services bound to internal network with reverse proxy
- Firewall Configuration: Configure firewalld/iptables rules
- Strong Passwords: Random password generation implemented
- Regular Updates: Automated security updates for container images
- Audit Logging: Structured logging with unique identifiers
- Resource Limits: systemd resource quotas configured
- Capability Dropping: Minimal Linux capabilities per service
- Read-only Root Filesystem: Mount containers with read-only root where possible
- User Namespaces: Rootless Podman user namespace isolation
- Security Scanning: Regular vulnerability scanning (Trivy, etc.)
- SELinux/AppArmor: Mandatory access controls
- Intrusion Detection: Host-based IDS (AIDE, Wazuh)
- Backup Encryption: Encrypt volume backups at rest
- Multi-factor Authentication: MFA for web interfaces
- HIPAA Compliance: Review technical safeguards for PHI
- Disaster Recovery: Document and test backup/restore
- Monitoring & Alerting: Health monitoring (Prometheus/Grafana)
- Incident Response: Maintain runbooks for security incidents
- Orthanc Lua Webhook: Implement OnStableStudy callback to notify Mirth
- Mirth HTTP Listener: Create channel to receive Orthanc webhook requests
- Study Routing Logic: Configure destination routing based on study metadata
- HL7/FHIR Transformation: Convert DICOM metadata to healthcare standards
- Error Handling: Implement retry logic and failure notifications
- Cloud Connectors: Build destination adapters for AWS/Azure/GCP
- Test Data & Workflows: Provide sample DICOM studies for integration testing
All services have systemd resource limits to prevent resource exhaustion and ensure fair allocation:
| Service | CPU Quota | Memory Max | Tasks Max | Rationale |
|---|---|---|---|---|
| PostgreSQL | 200% (2 cores) | 2 GB | 512 | Database operations require adequate CPU/memory |
| Orthanc | 200% (2 cores) | 2 GB | 512 | DICOM image processing is CPU/memory intensive |
| Mirth | 150% (1.5 cores) | 1 GB | 256 | Integration engine with moderate resource needs |
| pgAdmin | 100% (1 core) | 512 MB | 128 | Lightweight web UI |
| Nginx | 100% (1 core) | 256 MB | 64 | Reverse proxy with minimal overhead |
View current limits:
systemctl --user show postgres.service --property=CPUQuotaPerSecUSec --property=MemoryMax --property=TasksMaxAdjust limits: Edit the [Service] section in the respective .container file:
[Service]
CPUQuota=150% # 1.5 CPU cores
MemoryMax=1G # 1 GB RAM
TasksMax=256 # Max 256 tasks/threads
Then reload and restart:
systemctl --user daemon-reload
systemctl --user restart orthanc.serviceMonitor resource usage:
podman stats --no-stream
systemctl --user status orthancAll services use systemd journal with unique identifiers for easy filtering:
| Service | SyslogIdentifier | View Command |
|---|---|---|
| PostgreSQL | postgres-quadlet | journalctl --user -t postgres-quadlet |
| Orthanc | orthanc-quadlet | journalctl --user -t orthanc-quadlet |
| Mirth | mirth-quadlet | journalctl --user -t mirth-quadlet |
| pgAdmin | pgadmin-quadlet | journalctl --user -t pgadmin-quadlet |
| Nginx | nginx-quadlet | journalctl --user -t nginx-quadlet |
View recent logs:
journalctl --user -t orthanc-quadlet -n 50 --no-pagerFollow logs in real-time:
journalctl --user -t nginx-quadlet -fFilter by time:
journalctl --user -t postgres-quadlet --since "1 hour ago"
journalctl --user -t mirth-quadlet --since "2026-02-12 09:00"Search logs:
journalctl --user -t orthanc-quadlet | grep ERRORExport logs:
journalctl --user -t orthanc-quadlet --since today -o json > orthanc_logs.jsonService health checks:
# Check systemd service status
systemctl --user is-active postgres orthanc nginx
# Nginx health endpoint
curl http://localhost:8080/health
# Orthanc system information
curl -u admin http://localhost:8080/orthanc/system
# PostgreSQL connection test
podman exec postgres pg_isready -U postgresContainer health:
podman ps --format "{{.Names}}\t{{.Status}}"
podman healthcheck run orthanc # If healthcheck definedImportant: All credentials are stored as Podman secrets. For security reasons, actual passwords are not documented in this README.
- After initial deployment, credentials are stored in
CREDENTIALS.md(excluded from git) - To view secrets:
podman secret ls - To rotate passwords, see the Secret Management section below
All web interfaces accessed via nginx reverse proxy:
- pgAdmin: http://localhost:8080/pgadmin/ (login with email:
admin@example.com) - Orthanc Web UI: http://localhost:8080/orthanc/ (login with user:
admin)
Direct access:
- Mirth Connect: https://localhost:8443 (web UI and Administrator client API)
- Orthanc DICOM:
localhost:4242- for DICOM client connections - PostgreSQL:
localhost:5432(user:postgres) - internal only, not published
See CREDENTIALS.md for the actual passwords.
- README.md: This file - complete project documentation
- docs/INTEGRATION.md: Integration architecture for Orthanc-Mirth communication (in development)
- docs/CONTRIBUTING.md: Contribution guidelines and development workflow
- docs/SECURITY.md: Security policy, HIPAA considerations, and vulnerability reporting
- docs/CHANGELOG.md: Version history and release notes
- LICENSE: MIT License
- Makefile: Command shortcuts for common operations
- CREDENTIALS.md: Service credentials (excluded from git)
Generate sample config:
podman exec orthanc Orthanc --config=- > orthanc-sample.jsoncService fails to start:
# Check service status and logs
systemctl --user status orthanc.service
journalctl --user -t orthanc-quadlet -n 50 --no-pager
# Verify dependencies are running
systemctl --user status postgres.service
# Check for port conflicts
ss -tlnp | grep 4242Secret not found error:
# Verify secret exists
podman secret ls | grep orthanc_registered_users
# Recreate secret if missing
echo -n '{"admin": "password"}' | podman secret create orthanc_registered_users -Database connection errors:
# Verify PostgreSQL is accepting connections
podman exec postgres pg_isready -U postgres
# Check database exists
podman exec postgres psql -U postgres -c "\l"
# Verify user permissions
podman exec postgres psql -U postgres orthancdb -c "\du"Permission denied errors:
# Verify volume ownership
podman volume inspect orthanc_data
# Check user namespace mapping
podman unshare cat /proc/self/uid_map
# Restart with proper permissions
systemctl --user restart orthanc.service- Issues: Open an issue on GitHub with logs and configuration
- Discussions: Use GitHub Discussions for questions and ideas
- Security: Report vulnerabilities privately per docs/CONTRIBUTING.md
Contributions are welcome! Please see docs/CONTRIBUTING.md for:
- Development setup instructions
- Code style guidelines
- Pull request process
- Testing requirements
Priority areas for contribution:
- Orthanc-Mirth Integration (highest priority): Lua webhook and Mirth channel implementation
- Cloud Destination Connectors: AWS S3, Azure Blob Storage, GCP Cloud Storage adapters
- FHIR Resource Generation: Convert DICOM metadata to FHIR ImagingStudy resources
- Sample Data & Test Workflows: DICOM test datasets and integration examples
Additional areas:
- TLS/SSL configuration and certificate management
- Monitoring dashboards (Prometheus, Grafana)
- Advanced anonymization and de-identification
- Documentation improvements
- Security enhancements
See Project Status for the full development roadmap.
This project is licensed under the MIT License - see the LICENSE file for details.
Built with:
- Podman - Daemonless container engine
- PostgreSQL - Advanced open source database
- Orthanc - Lightweight DICOM server
- Mirth Connect - Healthcare integration engine
- Nginx - High-performance web server
Bridging Healthcare Data to the Cloud | Professional radiology and clinical integration platform