Skip to content

Deployment

xjoker edited this page Jul 21, 2026 · 2 revisions

Deployment

delamain always runs the same two processes — a Java backend (jadx headless API, binds 127.0.0.1:8650 inside its host) and a Python MCP gateway (binds 0.0.0.0:8651) — but there are several ways to get them running. Every method below publishes only port 8651; port 8650 is never reachable from outside the process/container that owns it. See Configuration for the full env-var/TOML reference used in these examples, and FAQ for why 8650 is never exposed.

(a) Docker Compose (recommended)

Uses the repo's docker-compose.yml, which builds (or pulls) the fused image, mounts your APK directory, and wires the required token.

# 1. Put the APK(s)/JAR(s)/DEX(s) you want to analyze in a directory
export JADX_APK_DIR=/data/apks

# 2. Set at least one MCP client token (comma/newline separated whitelist) — required, no default
export DELAMAIN_AUTH_TOKENS="$(openssl rand -hex 32)"

docker compose up -d
curl -s http://127.0.0.1:8651/health
  • Exposed ports: 127.0.0.1:${JADX_GATEWAY_PORT:-8651} → container 8651 only. The Java backend's 8650 is never published.
  • Volumes: ${JADX_APK_DIR:-/data/apks}/apks (read-write — uploads land here too); named volumes jadx-output/data/output and jadx-indices/data/indices (persist decompile cache / index across restarts); ${JADX_CONFIG_DIR:-/data/config}/data/config (read-only, holds config.toml).
  • Health check: built into the compose file — python3 -c "urllib.request.urlopen('http://127.0.0.1:8651/health', ...)", 30s interval, 120s start period.
  • Compose hard-fails at startup if DELAMAIN_AUTH_TOKENS is unset (${DELAMAIN_AUTH_TOKENS:?...} in the compose file).
  • JADX_INTERNAL_TOKEN (gateway↔Java internal token) is optional here — docker/entrypoint.sh auto-generates one and shares it between the two in-container processes if you don't set it.

(b) docker run (single container, equivalent to Compose)

Same image, explicit flags instead of a compose file:

docker run -d --name delamain \
  -p 127.0.0.1:8651:8651 \
  -v /data/apks:/apks \
  -v jadx-output:/data/output \
  -v jadx-indices:/data/indices \
  -v /data/config:/data/config:ro \
  -e JADX_FILE_ROOT=/apks \
  -e DELAMAIN_AUTH_TOKENS="$(openssl rand -hex 32)" \
  --log-opt max-size=10m --log-opt max-file=3 \
  ghcr.io/xjoker/delamain:latest
  • Exposed ports: 8651 only (bind it to 127.0.0.1 unless you intentionally want it reachable beyond localhost — put a reverse proxy/TLS terminator in front for real exposure).
  • Volumes: identical roles to Compose above — /apks read-write, /data/output and /data/indices persistent, /data/config read-only.
  • docker/entrypoint.sh starts the Java backend on 127.0.0.1:8650 first, waits for its /health, then execs the gateway in the foreground on 0.0.0.0:8651 — this is identical whether launched via docker run or Compose, since both use the same image/entrypoint.

(c) Prebuilt image (GHCR) vs. building locally

Pull the prebuilt image — fastest path, no local Maven/Java/Python toolchain needed:

docker pull ghcr.io/xjoker/delamain:latest

Both docker-compose.yml and the docker run example above default to ghcr.io/xjoker/delamain:latest.

Build locally instead (e.g. to test an unreleased change) — point Compose at the Dockerfile:

docker compose build   # uses docker/Dockerfile, context: repo root
docker compose up -d

The Dockerfile is a 3-stage multi-arch build (linux/amd64, linux/arm64): a Maven stage compiles the Java fat JAR and installs jadx-all (not on Maven Central) into the local m2 repo, a Python stage resolves gateway dependencies from gateway/requirements.lock with hash verification, and a slim JRE+python3 runtime stage assembles both. It runs as a non-root UID (10001) and only ever EXPOSEs 8651.

(d) Build from source / bare-metal (Java backend + Python gateway as separate processes)

For local development without Docker — see docs/dev-guide.md for full detail, summarized here.

Java backend:

# one-time: install jadx-all into the local Maven repo
wget https://github.com/skylot/jadx/releases/download/v1.5.6/jadx-1.5.6.zip -O /tmp/jadx.zip
unzip /tmp/jadx.zip -d /tmp/jadx
mvn install:install-file \
  -Dfile=$(find /tmp/jadx/lib -name 'jadx-*-all.jar' | head -1) \
  -DgroupId=io.github.skylot -DartifactId=jadx-all \
  -Dversion=1.5.6 -Dpackaging=jar

mvn -DskipTests clean package
java -jar target/delamain-*.jar --port 8650 --auth-token "$DELAMAIN_AUTH_TOKEN"

Python gateway (separate terminal):

cd gateway
uv sync
DELAMAIN_AUTH_TOKEN="$DELAMAIN_AUTH_TOKEN" \
DELAMAIN_AUTH_TOKENS="$DELAMAIN_AUTH_TOKENS" \
uv run python main.py --jadx localhost:8650
  • Exposed ports: whatever you bind them to yourself — nothing is containerized, so it's on you to firewall 8650 off if the two processes aren't on the same trusted host. In the fused container this is enforced by binding Java to 127.0.0.1; bare-metal you must do the equivalent (bind Java to loopback, or firewall it).
  • Volumes/persistence: none automatic — pass --index-dir / --output-dir to the Java backend yourself if you want decompile cache and index files to persist across restarts.
  • Java requires JDK 21 + Maven 3.9+; the gateway requires Python 3.10+ and uv.

(e) Prebaked index volume (large APKs on low-spec hosts)

For a multi-hundred-thousand-class APK on a memory-constrained host, build the index once on a large-heap machine and ship the resulting --index-dir to the target host so it starts in seconds (FAST_RESTORE) instead of doing a full cold decompile + index build. Full detail in docs/prebaked-index.md; summary:

# 1. On a large-heap machine, with the target APK already loaded, run one full warmup
curl -X POST http://localhost:18650/cache/warmup
curl -s http://localhost:18650/cache/warmup-status | jq '.running, .phase'

# 2. Package and ship the whole --index-dir directory (don't cherry-pick files)
tar -C /data/jadx-index -czf jadx-index-<inputHash>.tar.gz .
scp jadx-index-<inputHash>.tar.gz low-spec-host:/tmp/

# 3. On the low-spec host, unpack into the same --index-dir path and start
#    against the exact same APK file (FAST_RESTORE matches by content hash)
mkdir -p /data/jadx-index
tar -C /data/jadx-index -xzf /tmp/jadx-index-<inputHash>.tar.gz
java -jar delamain.jar --index-dir /data/jadx-index --apk /path/to/same.apk ...

# 4. Verify the volume is complete before trusting FAST_RESTORE
curl -s http://localhost:18650/index-stats | jq '.index_prebaked'

The volume includes an mmap'd shard index (.shard.N / .shardcat), which — unlike a purely in-memory trigram index — consumes no JVM heap, so code search stays fully covered even on a constrained-heap host. Check index_prebaked.complete and compare <inputHash>.manifest.json's input_hash/tool_version against the target machine before relying on it.