How to easily self host am-i.exposed
Run am-i.exposed yourself with Docker either built locally or via the prebuilt images
Self-hosting software manually is one of those things that sounds more intimidating than it really is. The actual work is usually quite ordinary: read the project, understand what it expects, wire a few containers together, and stop pretending you need an app store for every new app.
That is especially true for am-i.exposed, which is not some sprawling service with ten moving parts and a mystery control plane hidden somewhere behind the curtain. If you already run your own Bitcoin node and your own mempool.space instance, hosting this application yourself is very manageable.
In this guide, I am going to assume two things are already true:
- you already run a Bitcoin node elsewhere;
- you already run a working mempool.space stack elsewhere.
You have two choices here: use the prebuilt images, or clone the repo and build it yourself for more sovereignty.
A quick note on self-hosted mempool backends
It is worth mentioning one caveat from the project documentation: not every self-hosted mempool stack behaves exactly like the public hosted instance and may produce incorrect results.
The documentation notes that if you want the closest behaviour, you should prefer using mempool with fulcrum over electrs to get the closest experience to using the public mempool.space website (which uses mempool/electrs on the backend).
Option 1: use the prebuilt images
This is the easiest manual path if you do not want to build anything locally.
The deployment documentation in the am-i.exposed repository shows that the project publishes two relevant images:
ghcr.io/copexit/am-i-exposed-umbrelghcr.io/copexit/am-i-exposed-tor-proxy
Despite the naming, you do not need to run Umbrel itself to use them. You can run the same images with plain Docker Compose, as long as you provide the expected environment variables.
Create a directory for your deployment
On your server, create a dedicated directory for this stack:
1
2
mkdir -p ~/am-i-exposed
cd ~/am-i-exposed
Then create a docker-compose.yml file like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
services:
web:
image: ghcr.io/copexit/am-i-exposed-umbrel:latest
container_name: am-i-exposed-web
restart: unless-stopped
ports:
- "3080:8080"
environment:
APP_MEMPOOL_IP: 192.168.1.50
APP_MEMPOOL_PORT: "4080"
APP_TOR_PROXY_IP: tor-proxy
APP_TOR_PROXY_PORT: "3001"
APP_MEMPOOL_HIDDEN_SERVICE: ""
depends_on:
- tor-proxy
tor-proxy:
image: ghcr.io/copexit/am-i-exposed-tor-proxy:latest
container_name: am-i-exposed-tor-proxy
restart: unless-stopped
environment:
PORT: "3001"
TOR_PROXY_IP: 127.0.0.1
TOR_PROXY_PORT: "9050"
Environment variables explained:
APP_MEMPOOL_IPshould point to the host or container name of your existing mempool backend;APP_MEMPOOL_PORTshould be the HTTP port exposed by that backend;APP_TOR_PROXY_IPpoints to the sidecar service name, which is whytor-proxyworks;APP_TOR_PROXY_PORTstays at3001unless you intentionally change the sidecar configuration;APP_MEMPOOL_HIDDEN_SERVICEcan stay blank unless you specifically want to surface a hidden service hostname.
Start the stack up
Start it with:
1
docker compose up -d
Then check logs if needed:
1
2
docker compose logs -f web
docker compose logs -f tor-proxy
At that point, the site should be available on port 3080 of your server, unless you changed the mapping.
Option 2: clone the repo and build it yourself
This path is for people who would rather build the site from source and not depend on prebuilt images.
Clone the repository, add your own docker-compose.yml at the repo root, and let Compose build both containers locally.
Clone the repository
1
2
git clone https://github.com/copexit/am-i-exposed.git
cd am-i-exposed
Create a repo-root compose file
At the root of the cloned repo, create docker-compose.yml with content like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
services:
web:
build:
context: .
dockerfile: Dockerfile.umbrel
container_name: am-i-exposed-web
restart: unless-stopped
ports:
- "3080:8080"
environment:
APP_MEMPOOL_IP: 192.168.1.50
APP_MEMPOOL_PORT: "4080"
APP_TOR_PROXY_IP: tor-proxy
APP_TOR_PROXY_PORT: "3001"
APP_MEMPOOL_HIDDEN_SERVICE: ""
depends_on:
- tor-proxy
tor-proxy:
build:
context: ./umbrel/tor-proxy
container_name: am-i-exposed-tor-proxy
restart: unless-stopped
environment:
PORT: "3001"
TOR_PROXY_IP: 127.0.0.1
TOR_PROXY_PORT: "9050"
Then start it exactly as requested:
1
docker compose up -d --build
This tells Compose to:
- build the main image from the repository’s main Docker build file;
- build the Tor sidecar from the repository’s Tor proxy Docker build file;
- create and start both containers in the background.
Why this works
The build flow is straightforward: Compose builds the main image from the repo, builds the Tor sidecar, and starts both containers together.
In other words, you are not improvising some unsupported setup. You are simply using the project’s own container logic outside Umbrel.
Which option should you choose?
Use the prebuilt image path if:
- you want the simplest deployment;
- you are comfortable trusting the published images;
- you want faster updates with less local build time.
Use the local build path if:
- you want more sovereignty over what you run;
- you want more privacy by building from source yourself;
- you want to inspect or modify the code before you deploy it.
Neither approach is inherently magical. One is more convenient. The other is slightly more sovereign. Pick the trade-off you actually value instead of performing ideology for strangers online.
Updating the deployment
This part matters more than people admit. Self-hosting is easy to start and boring to maintain, which is exactly why maintenance gets neglected.
The right update process depends on which option you chose.
Updating a prebuilt-image deployment
If you used published GHCR images, the workflow is simple:
1
2
docker compose pull
docker compose up -d
If you want to force recreation cleanly, you can do:
1
2
docker compose pull
docker compose up -d --force-recreate
This pulls newer tags and recreates the containers using the updated images.
Using latest is fine if you want the easiest update path. If you prefer tighter control, pin a specific version tag instead and update it deliberately when you are ready.
latestis convenient. Specific version tags are more predictable. Pick the trade-off you actually want.
Updating a local-build deployment
If you cloned the repo and build locally, the workflow is usually:
1
2
git pull
docker compose up -d --build
That fetches upstream changes, rebuilds the images locally, and recreates the containers.
If you want a slightly cleaner sequence:
1
2
3
git pull
docker compose build --no-cache
docker compose up -d
You probably do not need --no-cache every single time, but it is useful when you suspect cached layers are hiding a change.
Final thoughts
There is nothing exotic about hosting am-i.exposed yourself. It is a small web app, a connection to your mempool backend, and a Tor sidecar. That is all very manageable.
If you want the fastest path, use the prebuilt images. If you want the most independent path, clone the repo and build it yourself.
Both approaches are valid. The only bad option is pretending you need a branded dashboard to run software that is already simple enough to understand on its own.
