decompiler-explorer runs each decompiler in its own container behind one Traefik proxy
Decompiler Explorer! Compare tools on the forefront of static analysis, now in your web browser!
At a glance
- What is it?
- decompiler-explorer is a web front-end that compares decompiler output on small executables. Its own compose file and Dockerfile decide most of how it behaves in production, including which ports the proxy publishes and what a runner is allowed to consume.
- Who is it for?
- Judge decompiler-explorer by its compose file rather than by its front-end. Every runner inherits a 120 second timeout, a 900 second extended timeout, a 10 GB memory cap and a replica count of one, and the proxy publishes its own unauthenticated API on 8080 alongside the site.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 5, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Traefik terminates TLS and also publishes its own API on 8080
The compose file gives the proxy the job that includes its management interface. The traefik service runs image traefik:v3.6, reads the Docker socket as a read-only mount at /var/run/docker.sock, and publishes three ports. Two are declared in host mode with target and published both set, 80 and 443. The third is a plain "8080:8080" mapping.
Its command enables the swarm provider and its watch mode, sets exposedbydefault=false so only services that ask to be routed are routed, binds web to :80 and websecure to :443, and redirects the http entrypoint onto websecure with permanent=true. A plain http request therefore ends on https rather than being served in the clear.
The same command list turns on --api and --api.insecure, the router's management API served without authentication, and it sits on that third published port. So the container holding the site's certificates also exposes a view of its own routing state on a port mapped to the host. The socket mount being read-only limits what the proxy can change, but it still gives that container the ability to enumerate everything else running in the daemon.
Every runner inherits the same timeout and the same 10 GB memory limit
An anchor named x-decompiler holds what all the runners share, and each service inherits it. init is true, the service joins backend_net, it receives SERVER and DEBUG from the environment with defaults of http://explorer:8000 and 0, and it mounts the worker_auth_token secret before starting with four flags.
The command passed to each runner is a list of `--timeout ${DECOMPILER_TIMEOUT:-120}`, `--extended-timeout ${DECOMPILER_EXTENDED_TIMEOUT:-900}`, `--mem-limit-hard ${DECOMPILER_MEM_LIMIT_HARD:-10000000000}` and `--mem-limit-soft ${DECOMPILER_MEM_LIMIT_SOFT:-10000000000}`. Two things stand out in those defaults. The soft and hard memory limits are the same number, so there is no lower ceiling for a runner to react to before the hard one applies, and ten billion bytes is a per-runner ceiling rather than a ceiling for the stack. The timeouts are a short 120 second pass and a 900 second extended pass, each overridable by environment variable, which is how a slow decompiler gets a second window without editing the compose file.
Each service also declares deploy.replicas: 1 and caps its json-file log at 5 files of 50 MB. The production command line asks for --replicas 2, so the replica count lives in two different places for the same value.
The image runs a newer Python than the prerequisites ask for
The Dockerfile starts from python:3.12-slim while the prerequisite list asks for python >= 3.8, so the image pins a much newer interpreter than the documented floor. It installs libpq-dev, gcc, libc6-dev and curl, then clears the apt lists, so build tooling stays in the runtime layer.
The account is created with `useradd -ms /bin/false backend_user`, which gives the service a real user with no usable shell, and that user owns /opt/decompiler_explorer and becomes the USER for every later step. pipenv arrives as a user install with the PATH extended to /home/backend_user/.local/bin.
Dependencies are installed from Pipfile.lock alone: the file is copied in and `pipenv sync` runs, so the image follows the locked set. Local setup instead runs `pipenv install`, and the Pipfile itself is never copied into the image, only its lock. The image then creates media and staticfiles without copying anything into either, copies manage.py, entrypoint.sh, templates, static, decompiler_explorer and explorer, and finishes with EXPOSE 8000 and gunicorn started with four workers bound to 0.0.0.0:8000 on decompiler_explorer.wsgi, with entrypoint.sh as the entrypoint.
The dev server starts the front-end and no runner at all
Two commands bring up the Django side outside Docker:
pipenv run python manage.py migrate
pipenv run python manage.py runserver 0.0.0.0:8000The README attaches a warning to this path: it won't start any decompilers, just the frontend. So the server is up and nothing is behind it. To get one runner reachable, you export the address the runner should call back on and start a single named service:
export EXPLORER_URL=http://172.17.0.1:8000
docker-compose up binja --build --force-recreate --remove-orphansThat callback address is a bare IP rather than a service name, so it depends on the host's Docker networking rather than on anything in the compose file. The same variable is what the runner container reads as SERVER, and inside the compose network its default is http://explorer:8000 instead, which is why the value has to be exported by hand in this flow. binja is one named service, and the flags ask for a rebuild, a fresh container, and removal of containers left over from other services.
Four compose files cover the documented run modes
The repository root carries docker-compose.yml, docker-compose.dev.yml, docker-compose.prod.yml and docker-compose.s3.yml, alongside Dockerfile, Dockerfile.dev and entrypoint.sh. The README documents three containerised modes, development, production, and production with S3 storage, plus the non-container dev server, so the storage decision and the environment decision end up as separate files rather than only separate flags.
Every mode is entered through the same wrapper. `python scripts/dce.py init` comes first, then `python scripts/dce.py build` for the runner images and `python scripts/dce.py start`, with a bare start landing the UI on port 80 and 443. Production adds --prod with a replica count and an ACME contact address:
python scripts/dce.py start --prod --replicas 2 --acme-email=<your email>That ACME address is the contact passed for certificate issuance, which means the containerised proxy is the component that has to reach an ACME endpoint to serve HTTPS at all. The Python side of the project is a Django app with manage.py at the root and decompiler_explorer and explorer as the two package directories, while the runner definitions live under runners/ and the wrapper under scripts/.
Storage is either local media or an S3 endpoint, chosen by flags
The production command grows four options when files should leave the host:
python scripts/dce.py start --prod --replicas 2 --acme-email=<your email> --s3 --s3-bucket=<s3 bucket name> --s3-endpoint=<s3 compatible endpoint> --s3-region=<s3 region>Each of the four is spelled out with a value to fill in and none of them has a default shown, which is different from the variables inside the compose file, where SERVER and DEBUG both fall back to something when unset. The endpoint is described as S3 compatible, so a self-hosted object store is in scope rather than only Amazon's own service, and the region is passed separately from the endpoint because non-Amazon stores still expect one.
The image creates a media directory and copies nothing into it, so the local case starts from an empty directory that the container is expected to fill, and the S3 flags are the alternative to that. The separate docker-compose.s3.yml file is what makes the two cases distinct at the compose layer as well, so choosing S3 means both a longer command line and a different compose file.
Your keys decide which decompilers get built
The build step is one line, and the comment above it carries the condition: `python scripts/dce.py build` builds all decompilers with valid keys. Nothing in the repository supplies those keys, and the per-tool setup lives outside the top-level README, under runners/decompiler/tools/README.md, which is where the instructions for setting up individual decompilers actually sit.
Exclusion is documented by example, commented out in the source:
python scripts/dce.py init
# Build all decompilers with valid keys
python scripts/dce.py build
# If you want to exclude certain decompilers
# python scripts/dce.py --without-reko build
python scripts/dce.py start
# UI now accessible on port 80/443The named exclusion is reko, so the flag takes a tool name and the build set is decided by which tools you hold keys for rather than by a fixed list. Since init is the only setup step that covers the Python side, a start without a build leaves the same situation as the dev server path: a front-end with no runner behind it. The repository publishes no releases, so there is no tagged version of that runner set to pin against; the lock file covers the Python dependencies only, and the decompiler inventory is whatever sits under runners/ at the commit you checked out.
Editorial conclusion
Judge decompiler-explorer by its compose file rather than by its front-end. Every runner inherits a 120 second timeout, a 900 second extended timeout, a 10 GB memory cap and a replica count of one, and the proxy publishes its own unauthenticated API on 8080 alongside the site. Check those three things against your own host before deploying: the exposed port, the docker socket handed to the proxy, and whether you hold the keys for the decompilers you want built. For comparing decompiler output on small executables it is a well-shaped tool; for anything larger than the context it was built around, the timeout defaults and the small-executable framing tell you where it stops.
Frequently asked questions
What is Decompiler Explorer used for?
It is a web front-end to a number of decompilers that lets you compare the output of different decompilers on small executables, hosted at dogbolt.org. The project frames it as the same idea as Compiler Explorer run in reverse.
How do I set up Decompiler Explorer locally?
The prerequisites are python >= 3.8, pipenv, docker and docker-compose, followed by two commands: pipenv install and python scripts/dce.py init. Setup for the decompilers themselves is documented separately under runners/decompiler/tools.
Does Decompiler Explorer need keys for its decompilers?
The build step is commented as building all decompilers with valid keys, so keys come from you, per tool. Individual decompilers can be left out, for example with python scripts/dce.py --without-reko build, which names reko as the tool to skip.
Can Decompiler Explorer run without Docker?
Only the front-end. pipenv run python manage.py migrate and pipenv run python manage.py runserver 0.0.0.0:8000 bring up the Django side, and the README notes that this path starts no decompilers. A single runner is then started separately with docker-compose up binja.
Which ports does Decompiler Explorer expose?
Traefik publishes 80 and 443 in host mode and maps 8080 as well, while the application container itself listens on 8000 with gunicorn bound to 0.0.0.0. The compose command also starts the Traefik API with --api --api.insecure.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/decompiler-explorer-decompiler-explorer)