OpenGrok becomes most useful around the third time you ask, “who calls this function across twelve repositories?” The web interface is the visible half; the real product is a reproducible indexing pipeline whose source snapshot, ctags parser, SCM tools, generated configuration, and Lucene data stay in sync. A container packages those moving pieces without pretending operations disappear.

Architecture in one minute

  • The source root contains checked-out repositories, usually one project per top-level directory.

  • The indexer reads source, symbols, and optional SCM history and writes index/configuration data.

  • The data root persists indexes, configuration, suggestions, and related generated state.

  • The web application reads source and indexes to serve search, definitions, references, history, and navigation.

  • A sync/reindex schedule makes the searchable snapshot converge with repository state.

Size and security before installation

  • Initial indexing is CPU, memory, and I/O intensive; the official guide commonly expects an indexer heap around 8 GiB for substantial code bases.

  • Repository history increases time and storage. Begin without unnecessary history when validating capacity.

  • Source search can expose secrets, unpublished vulnerabilities, credentials committed by mistake, and proprietary code.

  • Bind to loopback for a personal instance; use authenticated TLS reverse proxy and network policy before sharing.

  • Treat Docker daemon access as privileged host access and do not add users to its group casually.

Prepare source and persistent data directories

Ubuntu terminalbash
mkdir -p "$HOME/opengrok/src" "$HOME/opengrok/data"
cd "$HOME/opengrok/src"
git clone --depth=1 https://github.com/githubtraining/hellogitworld.git
find "$HOME/opengrok/src" -maxdepth 2 -type d -name .git -print
/home/user/opengrok/src/hellogitworld/.git

The top-level directory becomes a project

  • Use repositories you are authorized to index; the public training repository is only a small test fixture.

  • A shallow clone reduces history work but also limits history browsing.

  • Keep source and generated data separate so indexes never pollute repositories.

  • Local source is preferable because indexing is I/O intensive.

  • Scan repositories for secrets and define access controls before exposing results.

Pin the official image in Compose

~/opengrok/compose.yamlyaml
services:
  opengrok:
    image: opengrok/docker:1.14.15
    container_name: opengrok
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      NOMIRROR: "true"
      SYNC_PERIOD_MINUTES: "60"
    volumes:
      - ./src:/opengrok/src:ro
      - ./data:/opengrok/data:rw
    security_opt:
      - no-new-privileges:true

Every mount and port expresses policy

  • A numbered tag makes upgrades deliberate; verify the current official tag and digest before deployment.

  • Loopback binding prevents direct remote access, unlike publishing on every interface.

  • NOMIRROR tells the image that you manage repository checkouts outside the container.

  • The source mount is read-only; generated data remains writable and persistent.

  • The sync period controls scheduled indexing behavior in this container workflow; validate cadence against repository size.

  • no-new-privileges narrows one escalation path but is not a complete container sandbox.

Start and watch the initial index

~/opengrokbash
docker compose pull
docker compose up -d
docker compose logs -f --tail=200 opengrok
... indexing ...
... configuration ...
... Server startup ...

Risk level: caution. Review the command before running it.

Do not mistake container health for index readiness

  • pull downloads the pinned image; verify provenance/digest in controlled environments.

  • up -d creates persistent container state and publishes only the declared local port.

  • Logs reveal ctags failures, permissions, out-of-memory events, malformed files, and indexing completion.

  • Large first indexes can take hours; subsequent indexes are generally incremental.

  • Press Ctrl-C to stop following logs; it does not stop the detached service.

Ubuntu terminalbash
curl -fsS -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:8080/source/
docker compose -f "$HOME/opengrok/compose.yaml" ps
HTTP 200
NAME       IMAGE                       STATUS       PORTS
opengrok   opengrok/docker:1.14.15     Up ...       127.0.0.1:8080->8080/tcp

A useful acceptance test goes beyond HTTP 200

  • Open http://127.0.0.1:8080/source/ and confirm the expected project appears.

  • Search a known symbol and follow its definition and references.

  • Browse a file and, if enabled, verify history against the checked-out repository.

  • Confirm a remote machine cannot reach port 8080 directly.

  • Record index completion time, data size, and peak memory for capacity planning.

Update source without giving OpenGrok write access

Host source checkoutbash
git -C "$HOME/opengrok/src/hellogitworld" pull --ff-only
docker compose -f "$HOME/opengrok/compose.yaml" restart opengrok
docker compose -f "$HOME/opengrok/compose.yaml" logs -f --tail=100 opengrok
Already up to date.
... indexing ...

Separate synchronization from indexing

  • --ff-only refuses an unexpected merge in the indexed checkout.

  • Production synchronization should use a locked script/service and per-repository error reporting.

  • The official image schedules indexing; restart is a simple lab trigger, not the most efficient large-scale reindex API.

  • Do not update a huge tree throughout indexing without understanding snapshot consistency.

  • Monitor stale-index age and failures rather than assuming cron-like work succeeded.

Expose it through an authenticated reverse proxy

  • Keep the container port on loopback.

  • Terminate HTTPS at a maintained reverse proxy.

  • Require SSO, mTLS, VPN, or another organization-approved authentication layer.

  • Preserve the /source context path and proxy headers correctly.

  • Limit request sizes/rates and log access without leaking search terms unnecessarily.

  • Do not place private source search on the public internet merely because no repository write action exists.

Back up what can and cannot be rebuilt

  • Repositories should have authoritative upstream remotes or separate backups.

  • Indexes are rebuildable but expensive; snapshotting the data root can shorten recovery if version-compatible.

  • Preserve Compose configuration, image digest, proxy/auth configuration, sync scripts, exclusions, and operational secrets.

  • Test restoration and reindexing; a copied live index is not automatically a consistent backup.

  • Never store credentials inside the indexed source tree.

Upgrade without gambling the index

  1. Read OpenGrok release notes and container documentation.

  2. Snapshot configuration and persistent data, and ensure source can be reconstructed.

  3. Pull the intended numbered tag or immutable digest in staging.

  4. Rebuild/reindex representative repositories and exercise searches.

  5. Update production Compose deliberately, observe logs, and retain rollback artifacts.

  6. Do not use latest as an unattended production upgrade policy.

Troubleshooting map

  • No projects: source mount is empty/wrong, permissions prevent reads, or initial indexing failed.

  • Symbols missing: Universal Ctags failed, language analysis is unsupported, files are excluded, or index is stale.

  • Permission denied under data: host directory ownership/labels do not allow container writes.

  • Killed with exit 137: memory limit or host OOM ended Java; size heap/container resources based on evidence.

  • History absent: checkout is shallow, SCM executable/history option is missing, or repository metadata is unreadable.

  • UI shows old files: source sync and index completion are separate; inspect timestamps/logs.

  • Works locally but not through proxy: context path, forwarded headers, websocket/API route, authentication, or timeout policy is wrong.

Manual deployment requirements

Primary OpenGrok references