OTOBO Docker: More Secure and Performant with Hardened Docker Compose

2026-07-11

A practical guide to hardened OTOBO Docker Compose — network segmentation, non-root containers, nginx TLS proxy, resource limits, and a hardening overlay for the official RotherOSS stack.

OTOBO Docker: More Secure and Performant with Hardened Docker Compose

The official RotherOSS/otobo-docker stack gets OTOBO up and running quickly — web, MariaDB, Elasticsearch, Redis, and optionally Nginx in just minutes. For production environments, "it runs" is often not enough: volume permissions, missing network isolation, root containers, and unpinned images are typical vulnerabilities that admins usually only discover under load or during a security audit.

This article summarizes how to secure and tune OTOBO Docker without bypassing the official image release cycle.


Initial Situation: What the Standard Stack Does Well — and Where It Falls Short

AspectOfficial StackTypical Production Gap
InstallationProven Compose snippets, .env samplesMultiple override files in COMPOSE_FILE — hard to keep track of
TLSIntegrated nginx overrideSecurity headers, rate limits, and asset caching often minimal
NetworkA single shared Docker networkDB, Redis, and Elasticsearch theoretically reachable from the proxy
Container HardeningFunctionally designedRoot UIDs, open volume modes (777), broad capabilities
PerformanceDefaultsMariaDB/Redis/ES not tuned for ticket workloads
Operationsscripts/update.shNo least-privilege model for technical admins

Starting with OTOBO 11.1, OTOBO Core and OTOBO Docker have independent release cycles — a good time to intentionally own the infrastructure layer rather than just pulling images.


Two Sensible Approaches

1. Hardening Overlay (for Existing otobo-docker Installations)

If you are already using the official stack, you can retrofit a Compose override plus a host script without losing the upstream workflow:

  • Non-root UIDs for web, daemon, db, redis, and Elasticsearch
  • Config bind mount to /opt/otobo-config/ (read-only in the container)
  • Volume permissions changed from 777 to service-specific owners (e.g., 1000:1000, 999:999)
  • Least-privilege sudo for the otobo-admin group (only docker ps/logs, sudoedit on config, no root shell)

The overlay is appended to COMPOSE_FILE and applied idempotently via script — including a backup of the Docker volumes before restarting.

2. Project-Owned Compose Stack (Greenfield / Controlled Upgrades)

For new deployments, a dedicated, streamlined Compose stack under docker/ is worth setting up:

text
docker-compose.yml          # Base topology (db, redis, elastic, web, daemon)
docker-compose.dev.yml      # HTTP on :8080, DB localhost only
docker-compose.prod.yml     # Nginx TLS proxy, no direct host ports
config/mariadb/otobo.cnf    # InnoDB tuning (utf8mb4, buffer pool)
config/redis/redis.conf     # maxmemory + LRU eviction
config/nginx/               # TLS, security headers, static cache, rate limits

Design Principles:

PrincipleImplementation
ReproducibilityImage tags pinned in .env; upgrade = tag bump + scripts/upgrade.sh
Defense in Depthcap_drop: ALL, no-new-privileges, non-root where possible
Network Segmentationfrontend (nginx ↔ web) and backend (datastores) — DB/Redis/ES never exposed to the proxy
PerformanceMariaDB innodb_buffer_pool_size, Redis maxmemory, ES ES_JAVA_OPTS configured
ObservabilityHealth checks with depends_on: condition: service_healthy
Resource GovernanceCPU/memory limits per service

In production, only nginx terminates TLS; the OTOBO web container stays internal on port 5000.


Security in Detail

Network: Frontend vs. Backend

yaml
networks:
  frontend: # nginx ↔ web
  backend: # web/daemon ↔ db/redis/elastic

The database and cache are not on the same network as the reverse proxy. Even if the nginx container is compromised, direct access to MariaDB remains blocked.

Container Hardening

Every service starts with:

  • cap_drop: [ALL] plus minimally required cap_add
  • security_opt: [no-new-privileges:true]
  • Dedicated Linux users (mysql:mysql, redis:redis, UID mapping for OTOBO)

Nginx as the Single Entry Point

The production override sets up:

  • HTTP → HTTPS redirect
  • TLS 1.2/1.3 (Mozilla Intermediate Profile)
  • HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
  • Rate limiting and connection limits on dynamic requests
  • Proxy cache for /otobo-web/ assets (skins, JS, CSS) — offloads Perl/PSGI

Config Outside the Container

Config.pm and Kernel/Config/Files/ reside on the host under /opt/otobo-config/ and are mounted read-only. Changes are made via sudoedit — not via docker exec as root inside the running container.


Performance in Detail

MariaDB

  • innodb_buffer_pool_size ≈ 70–80% of the DB container limit
  • innodb_log_file_size 512M (fewer checkpoint churns than the 256M minimum)
  • max_allowed_packet 128M for large ticket attachments
  • utf8mb4 + dynamic row format (mandatory for OTOBO)

Redis

  • Pure cache: maxmemory 512mb with allkeys-lru
  • No host port — internal backend network only

Elasticsearch

  • bootstrap.memory_lock: true + matching ulimits
  • JVM heap ≤ 50% of the container memory limit
  • Host kernel: vm.max_map_count=262144

Nginx Static Cache

Static assets are cached at the proxy for seven days (X-Cache-Status header for debugging). Dynamic ticket requests remain uncached but rate-limited.


Resource Budget (Reference Values)

ServiceCPURAMNote
MariaDB22 GBKeep buffer pool in sync
Elasticsearch22 GBHeap ≤ 1 GB
Web (PSGI)22 GBOTOBO_WEB_OPTION=deployment in prod
Daemon11 GBGenericAgent, escalations
Redis1768 MBmaxmemory < limit
Nginx1256 MBprod only

Minimum Host: 8 GB RAM, 2 vCPUs, 50 GB SSD. For productive teams, 16 GB is recommended.


GitHub Repo: Yes — and Where?

Yes, a public repo makes sense — for three reasons:

  1. Reusability: Other OTOBO admins are looking for these exact patterns (hardening, nginx, tuning).
  2. Transparency: Security-relevant infrastructure belongs in versioned, reviewable files — not just in client projects.
  3. SEO & Community: Linking from the Open ITSM Hub and OTOBO documentation strengthens the ecosystem.

Suggested Repo Name: otobo-docker-production — as a community infrastructure, not as a fork of RotherOSS.

Structure:

text
otobo-docker-production/
├── overlay/                 # For users of the official RotherOSS stack
│   ├── otobo-override-hardening.yml
│   ├── harden-docker.sh
│   └── otobo-admin.sudoers
├── compose/                 # Complete project-owned stack
│   ├── docker-compose.yml
│   ├── docker-compose.dev.yml
│   ├── docker-compose.prod.yml
│   ├── config/
│   └── scripts/
└── README.md                # Decision guide: Overlay vs. Compose

What Does Not Belong in the Repo

Do Not Fork UpstreamInstead
RotherOSS/otobo-dockerReference override files, link upstream in README
RotherOSS/otobo imagesPin tags, pull images
Client-specific .env secrets.env.example with placeholders
TLS private keys.gitignore + cert generation script

Upstream Contributions

Generic improvements (e.g., default volume permissions, documented non-root UIDs) can be submitted as issues or PRs to RotherOSS/otobo-docker — the overlay approach remains independent of merge speeds.


Quick Start (Compose Stack)

bash
cd otobo-docker-production/compose
cp .env.example .env
# Set OTOBO_DB_ROOT_PASSWORD

# Development (HTTP)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d

# Production (TLS)
./scripts/gen-selfsigned-cert.sh helpdesk.example.com   # or a real certificate
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Web installer: https://<fqdn>/otobo/installer.pl — DB host = db.


Quick Start (Hardening Overlay)

For existing /opt/otobo-docker installations:

bash
# After cloning the overlay/ folder
bash harden-docker.sh              # Volume fix, override, sudoers, restart
bash harden-docker.sh --dry-run    # Preview only
bash harden-docker.sh --add-admin max.mustermann

Conclusion

The official OTOBO Docker stack is the right entry point. For production, building an intentional layer on top pays off:

  • Overlay — fast, upstream-compatible, for existing installations
  • Dedicated Compose stack — maximum control, clear dev/prod separation, reproducible upgrades

Both are valuable open source contributions — and belong in a dedicated repo, not in open-itsm-hub (directory/content) and not as a fork of RotherOSS.


Further Reading