Self-hosting is hereRead more
← Self-Hosted

Installing

One command on your server, then one link to click. Roughly five minutes, most of it waiting for a download.

Everything below is also in the install repository, alongside the scripts themselves. That repository is public and its scripts are linted and the compose file validated on every change — you are about to run a shell script on your own machine, and that should be checkable rather than a claim.

Self-hosted ArmoryHub is a beta product. Same application, same encryption as the hosted version — but younger, running on hardware we have never seen. Expect rough edges, and keep a backup you have actually tested.

It is free during the beta: no licence key, no activation, nothing in the instance that phones home to check one. When licensing does arrive, any instance running a version below 1.0 is grandfathered and stays free permanently. More on that, including how to pay something if you want to.

Before you start

A machine that stays on, running Linux. A NAS, a mini PC, an old laptop or a VM is plenty. Both 64-bit Intel/AMD (amd64) and 64-bit ARM (arm64) are supported, so a Raspberry Pi 4 or 5 works.

MinimumComfortable
Memory1 GB free2 GB free
Disk3 GB, plus room for photos10 GB+
CPUany 64-bit dual core

The install downloads roughly 150 MB. Photos are the only thing that grows much over time — budget for your existing photo library, plus room for backups, which keep 7 daily and 4 weekly copies.

You also need:

  • Docker with the Compose plugin, v2.23 or newer, working as your normal user without sudo. Older Compose cannot read the setup scripts embedded in the compose file and fails with a confusing error about configs.
  • curl — Debian does not ship it.
  • A Tailscale account with MagicDNS and HTTPS Certificates enabled. Both are free and both are off by default — see below. Or skip Tailscale entirely if you run your own reverse proxy.
  • The Tailscale app on every device you want to use ArmoryHub from, signed in to the same account.

You do not need a domain name, a static IP, port forwarding, any firewall changes, or root access beyond installing Docker. Nothing is exposed to the internet.

If it is a virtual machine, set the CPU type to host

Proxmox, and most other hypervisors, default to a generic emulated CPU — kvm64 or qemu64 — which hides the real processor's AES-NI and AVX2 instructions from the guest. Those are exactly the instructions HTTPS and password hashing use, so the default quietly forces both onto slow software fallbacks and makes signing in and loading pages sluggish for no benefit at all.

  • Proxmox: Hardware → Processors → Type: host
  • libvirt / virt-manager: tick Copy host CPU configuration, or set <cpu mode='host-passthrough'/> in the domain XML
  • VMware, Hyper-V, UTM: usually called CPU pass-through or “expose host features”

You can change this on a VM you have already built: shut it down, change the type, start it again. Nothing inside ArmoryHub needs touching. The only thing pass-through costs you is live-migrating that VM to a machine with a different CPU, which is not something a single home instance does.

Docker, and the group that trips everyone up

Docker and Compose must both work as your normal user, without sudo:

docker ps
docker compose version

If docker ps gives a permission error on /var/run/docker.sock, your user is not in the docker group. This is the single most common way a self-host install stalls.

sudo usermod -aG docker $USER

Then log out and back in. Group membership only applies to new login sessions, so until you do, docker keeps failing and it looks like the install did not work. Check it took with docker run hello-world. On Debian you also need curl, which is not installed by default:

sudo apt-get update && sudo apt-get install -y curl

Starting from a bare Debian or Ubuntu machine

There is an optional script that does the whole lot in one run: system update, sudo, curl, ca-certificates and SSH, Docker Engine with the Compose plugin, and both group memberships.

It is optional, and nothing runs it for you. Installing ArmoryHub never invokes it. It is a convenience for people who would rather not assemble the steps themselves, and skipping it is a perfectly normal way to install. It must run as root, which is exactly why you should read it first — bootstrap-debian.sh is about a hundred lines and every step is commented.

Becoming root is the one place the two distributions differ. Debian sets a root password during installation, so su - works. Ubuntu locks the root account, so su - there rejects your password no matter what you type — use sudo -i instead.

su -                                        # Debian: the root password you set
sudo -i                                     # Ubuntu: your own password

Then, identically on both:

apt-get update && apt-get install -y curl   # Debian does not ship curl
curl -fsSL https://raw.githubusercontent.com/marcjwrigg/armoryhub-selfhost-install/main/bootstrap-debian.sh -o bootstrap-debian.sh
less bootstrap-debian.sh                    # read it before running it
bash bootstrap-debian.sh <your-username>

Pass your everyday login name, not root — that is the account that gets the group memberships, and the account you should install ArmoryHub as. It is downloaded and read, then run as a separate deliberate step, rather than piped straight from the internet into a root shell.

When it finishes, log out completely and back in. exit on its own is not enough: the session underneath your root shell started before your user joined the groups, so it has not got them either. Leave the root shell and log out of your own session — over SSH, close the connection — then log back in and check with docker run hello-world.

It is safe to re-run and skips Docker if Docker is already installed. It works unchanged on Ubuntu and the other Debian derivatives, because it keys off apt-get rather than checking for a particular distribution. On anything else, install Docker via docs.docker.com and add yourself to the docker group by hand.

Tailscale, and why HTTPS is not optional

Browsers only allow the encryption ArmoryHub depends on over HTTPS, or on localhost. Reach the app by IP over plain http:// and you will be able to sign in and then fail to unlock your data — everything looks fine until the moment it doesn't. So the instance needs a real certificate, and Tailscale is by far the easiest way to get one.

If you have not used it before: Tailscale is a private network that links your own devices together, wherever they are. ArmoryHub uses it for two things — reaching your instance from your phone or laptop without exposing anything to the internet, and getting a real HTTPS certificate for it. It is free for personal use.

Create an account, then turn on two toggles that are free on every plan and off by default:

  1. Go to login.tailscale.com/admin/dns
  2. Enable MagicDNS if it is not already on
  3. Enable HTTPS Certificates

Miss those two and the install still fails at the last step

Everything installs correctly, then fails with your Tailscale account does not support getting TLS certs — which looks far worse than it is. Nothing is broken and nothing needs reinstalling; turn the toggle on and restart the Tailscale service.

Install the Tailscale app on every device you want to use ArmoryHub from — your phone, your laptop, anything else — signed in to the same account. Your instance lives on that private network, so its address only resolves on devices that have joined it. Nothing loads without it, which catches people who set the server up and then try to open it on a phone that is not connected yet.

Prefer your own certificates and reverse proxy? You can skip Tailscale entirely — see reverse proxies and your own domain.

Run the installer

On the server, as your normal user — not root:

curl -fsSL https://armoryhub.app/install.sh | sh

It checks Docker, generates every password and secret for you, pulls the image, starts everything including HTTPS, and finishes by printing a link to approve the machine on your Tailscale network. Nothing to edit and no keys to generate. That is the whole install: Postgres, the app, nightly backups, Tailscale and a real auto-renewing certificate, configured and running.

If you would rather read it before running it, the script is plain POSIX shell with no dependencies and published in full. Watch it come up with docker compose logs -f app: it applies the database schema and checks its own configuration before serving anything, and refuses to start rather than start wrong, so any failure is explained in that log.

Or set it up by hand

mkdir armoryhub && cd armoryhub
curl -fsSL https://raw.githubusercontent.com/marcjwrigg/armoryhub-selfhost-install/main/docker-compose.yml -o docker-compose.yml
curl -fsSL https://raw.githubusercontent.com/marcjwrigg/armoryhub-selfhost-install/main/env.example -o .env
chmod 600 .env
# replace every CHANGE_ME — openssl rand -base64 32
# (and 48 for AUTH_JWT_SECRET)
docker compose up -d

Install-time options

Environment variables go after the pipe, so they reach the shell running the script rather than curl:

# Install somewhere other than ~/armoryhub
curl -fsSL https://armoryhub.app/install.sh | ARMORYHUB_DIR=/mnt/cache/appdata/armoryhub sh

# Choose the name on your tailnet (default: armoryhub)
curl -fsSL https://armoryhub.app/install.sh | ARMORYHUB_TS_HOSTNAME=armory sh

Put the database on ordinary local storage

This is the one that bites on NAS hardware. The database lives in ./data/postgres beside the compose file, and Postgres on a FUSE mount or a network share risks locking errors and corruption. On Unraid that means installing to a cache-backed path: /mnt/user/... goes through shfs, which is FUSE. The same applies to NFS and SMB. ARMORYHUB_DIR is how you move it.

Tailscale certificates are rate-limited per hostname — five a week. If you have reinstalled several times and HTTPS has stopped working, a fresh ARMORYHUB_TS_HOSTNAME sidesteps a limit you have already hit.

For an unattended install, put a reusable, non-ephemeral TS_AUTHKEY in .env before running, and it registers without waiting for anyone to click a link.

Approve the machine

Click the link the installer prints and sign in. That adds this machine to your tailnet, and the certificate is issued automatically. If the link expires before you use it, get a fresh one:

docker compose exec tailscale tailscale status

Open your instance and create your account

Your instance now lives at:

https://armoryhub.<your-tailnet>.ts.net

Bookmark that. It is the address to use from every device, and the only one the app can work on. That address has a real certificate, works from your phone and laptop anywhere, and is reachable only by your own devices. Nothing is exposed to the internet and you changed no router settings.

Use the HTTPS address, not the IP

If you reach the app at something like http://192.168.1.50:8477 you will be able to sign in and then fail to set a PIN — everything else will appear to work, which makes it a confusing way to fail. Use the https://…ts.net address.

If you run a dashboard like CasaOS, its “open app” button points at http://<server-ip>:8477 — that same unusable address. Ignore the button and use your bookmark. Do not try to fix it by editing the app's Web UI setting: saving that form rewrites your compose file and deletes services. See CasaOS, Portainer and Dockge.

Open your address and follow first-run setup. You will set a password to sign in, and then a PIN to decrypt your data — two different things, and the difference matters. Read the next section before you choose them.

Your password, PIN and passphrase

ProtectsRequired?Recoverable?
Passwordsigning inyesYes
PINdecrypting your dataalwaysNo. Never.
Passphrasethe copy of your key held on this serveroptionalNo. Never.

Reset a forgotten password:

armoryhub password

Your PIN decrypts your data, and nobody can reset or recover it — including us. It never leaves your device in a usable form, which is what makes the encryption meaningful. Forget it and your records are permanently unreadable.

A passphrase is optional, and it is not a backup for the PIN. What it does is raise the entropy of the key material this server holds. Without one, the copy stored here is protected by a six-digit PIN, which is weak against anyone who obtains the database. With one, the server holds a copy protected by something far stronger.

If you set a passphrase, it is also required — together with your PIN — to set ArmoryHub up on another device. That follows from where the key is protected; it is not the reason for having one.

So: write your PIN down, write your passphrase down if you set one, and keep both somewhere safe and offline.

The armoryhub command

The installer sets up an armoryhub command. Use it in preference to docker compose: it reads Docker directly, so it keeps working even if a dashboard has rewritten your compose file.

armoryhub doctor            # check everything and say what is wrong
armoryhub status            # what is running
armoryhub logs [service]    # follow logs, default: app
armoryhub url               # the address to open
armoryhub backup list       # what backups exist
armoryhub backup now        # take one immediately
armoryhub backup verify latest
armoryhub restore <stamp>   # confirms, then stops and restarts the app
armoryhub password          # reset the account password
armoryhub update            # pull and restart

armoryhub doctor is the one to run when something is wrong. It checks the containers, whether the app answers, whether HTTPS actually works end to end, whether your compose file has been rewritten, whether the database is on storage that will corrupt it, free space, and backup age — and tells you what to do about each.

If armoryhub says “command not found”, the installer could not write to /usr/local/bin — that needs sudo, which it will not use unattended. The helper is in your install directory instead, so either run it from there or put it on your PATH once:

~/armoryhub/armoryhub doctor
sudo install -m 755 ~/armoryhub/armoryhub /usr/local/bin/armoryhub

Doing it with docker compose instead

These work too, from your install directory (~/armoryhub unless you changed it). Anything with --profile will fail if a dashboard has rewritten the file.

docker compose ps                      # expect four: app, db, backup, tailscale
docker compose logs -f app             # what the app is doing, live
docker compose restart app             # after an .env change
docker compose images app              # which version is actually running

docker compose exec tailscale tailscale status         # is the node connected?
docker compose exec tailscale tailscale serve status   # is it proxying to the app?
docker compose exec tailscale tailscale cert <domain>  # force a certificate
docker compose logs tailscale | grep -i cert           # why a certificate failed

docker compose --profile tools run --rm restore list
docker compose --profile tools run --rm restore once
docker compose --profile tools run --rm restore verify latest

docker compose config --quiet          # exit 0 means the compose file is valid
sudo du -sh data/*                     # what is using space

tailscale cert is the one to reach for when the page is blank: it prints the real error, where the browser just shows you nothing.

Installing it as an app

ArmoryHub installs to your home screen or dock and then behaves like any other app — its own icon, its own window, no browser chrome. There is nothing to download from an app store.

First, on each device you want to use it from: install Tailscale from your app store, sign in to the same account, and make sure it is connected. Your instance lives on your private network, so the address only resolves while Tailscale is on. If the page will not load, that is almost always the reason.

iPhone or iPad

Use Safari. Tap the Share button (the square with an arrow pointing up), scroll down, tap Add to Home Screen, then Add. Other browsers on iOS can make a shortcut, but only Safari installs it as a real app.

Android

Use Chrome. Tap the three-dot menu, then Install app (some versions say Add to Home screen), then confirm.

Windows or Linux desktop

In Chrome or Edge, click the install icon at the right-hand end of the address bar, then Install. If you do not see it, the three-dot menu has the same option.

Mac

In Safari, choose File → Add to Dock. In Chrome or Edge, use the install icon in the address bar as above.

Launch it from your home screen or dock afterwards. It keeps you signed in, so you only need your PIN. Remember that Tailscale has to be connected for it to load — worth knowing before you rely on it at a range with no signal.

Backups

A nightly backup runs automatically and keeps 7 daily plus 4 weekly copies.

armoryhub backup list
armoryhub backup now
armoryhub backup verify latest

To restore — this replaces your current data, and asks you to confirm first:

armoryhub restore <stamp>

Test a restore before you rely on one. Backups are written to ./data/backups as ordinary files, so copy them off this machine periodically — a backup on the same disk does not survive that disk failing. Their contents are already encrypted, so cloud storage is low-risk.

Updating

armoryhub update

That checks what the current release is, moves your install to it, restarts everything, and updates the armoryhub command itself. A backup is taken before any database changes are applied, and the upgrade aborts if that backup fails. It prints what it did — 0.1.6 -> 0.1.7, or already on 0.1.7 — so you can tell the difference between an upgrade and a no-op. Confirm with armoryhub doctor.

How the version is pinned, because it explains what you will see in .env. Your docker-compose.yml pins an image tag, fixed at the moment you installed, so that an install is reproducible and you can always tell what you are running. Updating sets ARMORYHUB_VERSION in .env, which the compose file reads in preference to that pin. Nothing rewrites docker-compose.yml, so local edits to it survive.

To move to a specific version, or back to one:

armoryhub update 0.1.6

Downgrades are not generally safe once a release has changed the database — check the release notes before going backwards.

The equivalent by hand is:

docker compose pull && docker compose down && docker compose up -d

On its own that re-pulls the tag already pinned in your compose file and leaves you exactly where you were, so set ARMORYHUB_VERSION in .env first if you are doing it this way. Use down then up rather than up -d on its own — some versions of Docker Compose replace a container by renaming the old one and can leave it running, quietly continuing to serve the previous version.

Installed before 0.1.7? Replace the helper once

Every install made before 0.1.7 has an armoryhub update that cannot actually change the version: the image tag is pinned in docker-compose.yml at install time, and update pulled that same pinned tag — so it reported success, updated its own helper script, and left the app exactly where it was. If armoryhub doctor says you are on 0.1.6, this is why. The fix is in the new helper, which the old one cannot install for itself:

sudo curl -fsSL https://raw.githubusercontent.com/marcjwrigg/armoryhub-selfhost-install/main/armoryhub \
  -o "$(command -v armoryhub)" && sudo chmod +x "$(command -v armoryhub)"
armoryhub update

From then on armoryhub update keeps itself current and armoryhub doctor reports your version and whether it is the latest. New installs need none of this.

Reverse proxies and your own domain

If you already run nginx, Caddy, Traefik or Nginx Proxy Manager and want to use your own certificates, you do not need Tailscale at all. Pass your public HTTPS address and the installer skips the entire Tailscale phase — no sidecar, no machine to approve, no certificate to wait for:

curl -fsSL https://armoryhub.app/install.sh | sh -s -- --reverse-proxy https://armory.example.com

The -s -- matters. It is what lets a piped script take arguments at all; without it the shell treats --reverse-proxy as a filename to run. If you would rather not remember that, the environment form is equivalent:

curl -fsSL https://armoryhub.app/install.sh | ARMORYHUB_REVERSE_PROXY=https://armory.example.com sh

That sets APP_URL to your address, sets APP_BIND=0.0.0.0 so a proxy outside this host can reach the app, and leaves COMPOSE_PROFILES empty so no sidecar starts. Run install.sh --help for every option. Then point your proxy at http://<this-machine>:8477 and serve it as your HTTPS address — terminate TLS at the proxy, because a plain http:// address lets you sign in and then fails to unlock your data.

Which bind address you want

APP_BIND=0.0.0.0 is the default with --reverse-proxy because it is what the common setups need — but it publishes an unencrypted login page on port 8477 to your whole network, so it is worth knowing whether you need it.

Where your proxy runsWhat to use
Same host, host network mode--bind 127.0.0.1 — loopback is enough
Docker bridge network, or another machinethe default 0.0.0.0
Attached to this stack's own network--bind 127.0.0.1, and target app:3000

Until the proxy is actually in front of it, 0.0.0.0 means anyone on your LAN can reach that login page. Firewall the port, or use --bind 127.0.0.1 if you can.

Converting an existing install

Already installed with Tailscale and want to switch? Nothing needs reinstalling — edit .env in your install directory and restart:

cd ~/armoryhub
sed -i 's|^APP_URL=.*|APP_URL=https://armory.example.com|' .env
sed -i 's|^APP_BIND=.*|APP_BIND=0.0.0.0|' .env
sed -i 's|^COMPOSE_PROFILES=.*|COMPOSE_PROFILES=|' .env
docker compose --profile tailscale down
docker compose up -d

Sign out of Tailscale first if you are done with it entirely — docker compose exec tailscale tailscale logout — or a later reinstall shows up as armoryhub-1. If your install predates --reverse-proxy it may have no APP_BIND line at all, in which case add one rather than replacing it:

grep -q '^APP_BIND=' .env || echo 'APP_BIND=0.0.0.0' >> .env

Or use the built-in Caddy, with your own domain

If you own a domain and want a public URL without running your own proxy, the compose file ships a Caddy profile that gets a Let's Encrypt certificate for you. In .env:

CADDY_SITE_ADDRESS=armory.example.com
CADDY_ACME_EMAIL=you@example.com
APP_URL=https://armory.example.com     # must match CADDY_SITE_ADDRESS
COMPOSE_PROFILES=caddy

APP_URL has to be the same address as CADDY_SITE_ADDRESS. There is no --caddy install flag, so everyone arriving here already has an install whose APP_URL points at a .ts.net name. Leave it pointing there and the app keeps generating links to an address Caddy is not serving.

Then point a DNS record at the machine and docker compose up -d. This needs ports 80 and 443 reachable from the internet — unlike the Tailscale route, it does put your login page on the public internet.

Changing the installer itself

install.sh is a plain POSIX shell script with no dependencies, and the repository is public. If it does not do what your setup needs, clone it, edit it, and run your copy:

git clone https://github.com/marcjwrigg/armoryhub-selfhost-install.git
cd armoryhub-selfhost-install
# edit install.sh
sh install.sh --reverse-proxy https://armory.example.com

Set ARMORYHUB_CONFIG_REPO=you/your-fork if you also want it to pull docker-compose.yml and the armoryhub helper from your fork.

CasaOS, Portainer and Dockge

The compose file works in any of them, but CasaOS rewrites it when you save anything through its settings form, including when you set the Web UI address. Measured on a real install, saving once:

  • removes the tailscale, restore and caddy services, because CasaOS does not understand Compose profiles and drops what it cannot model. Containers already running keep running, so nothing appears wrong — but docker compose can no longer start or restart Tailscale, and reports no such service: tailscale.
  • converts $$ to $, so the embedded database setup would run with an empty username on a fresh install.
  • copies your passwords out of .env into the compose file, which is world-readable, while .env is not.

None of that breaks a running instance, which is why it goes unnoticed. It bites later, when something needs restarting.

The safest arrangement is to run this from a terminal and, if you want a tile on the CasaOS dashboard, add it as an external link to your https://…ts.net address rather than importing the compose file. You get the icon and the click-through with CasaOS having no authority over the configuration. If you do let CasaOS manage it, keep a copy of the original compose file — CasaOS moves what it replaces to docker-compose.yml.bak, owned by root.

Working around it without a fight

If you want to keep CasaOS managing the app, keep a second, pristine copy of the compose file under a name CasaOS does not touch, and use it for anything involving profiles:

cd ~/armoryhub
curl -fsSL https://raw.githubusercontent.com/marcjwrigg/armoryhub-selfhost-install/main/docker-compose.yml -o compose-full.yml

docker compose -f compose-full.yml --profile tools run --rm --no-deps restore list
docker compose -f compose-full.yml --profile tailscale up -d

The project name comes from the directory, so this attaches to the same containers, network and data as usual — it is the same stack, just described by a file CasaOS has not edited. --no-deps stops Compose recreating your database because the two files disagree.

Moving a hosted account across

Your data comes over as a single encrypted archive. Nothing is re-encrypted, and no key ever travels in a form anyone could use.

  1. On hosted: Settings → Data Management → Export Data. You get a .zip.
  2. On your instance: Settings → Data Management → Import Data, and select that file.
  3. Unlock with the secret from your hosted account — not the PIN you just set up here. The app reloads and asks for it.

Before it changes anything, it shows you which account the archive came from, when it was exported, how many records and files it contains, whether the account had a passphrase, and how many records a replace would remove. You have to tick a box acknowledging that before it will proceed.

Which secret you need, and why

The archive carries your master key wrapped, along with the salts needed to derive the key that unwraps it. The plaintext key is never in the file, and neither is your PIN or passphrase. That is why your existing secret still works on a completely different server: the new instance is not given your key, it is given something only your secret can open.

Your hosted accountWhat you need after importing
PIN onlyyour hosted PIN
PIN and a passphraseyour passphrase, then your hosted PIN

The second row surprises people. Enabling a passphrase deliberately removes the PIN-wrapped copy of your key from the server, so that the only copy we ever hold is protected by something far stronger than six digits. That copy is what ends up in your export — so the passphrase is the only thing that can open it.

If you set a passphrase on hosted and no longer have it, do not export and wipe anything

Your records cannot be decrypted without it, on either instance. Check that you have it before you start.

Afterwards, the PIN you created during self-hosted setup is gone — it belonged to the empty account the import replaced. From then on you unlock with the secret that came across, and you can change it in Security settings once you are in. Your hosted account is untouched by any of this: keep it until you are satisfied the migration worked, and compare a few records before you rely on the new instance. The same process works in reverse if you decide to move back.

Troubleshooting

Start with armoryhub doctor. It diagnoses most of what follows and tells you what to do about it.

The page is blank, or the certificate looks wrong

Ask Tailscale directly — it prints the real error where the browser shows you nothing:

docker compose exec tailscale tailscale cert <your-full-domain>

If that comes back with a 500 and your Tailscale account does not support getting TLS certs, nothing is broken and nothing needs reinstalling — HTTPS Certificates were never switched on for your tailnet. It is one toggle, free on every plan, off by default. Turn it on, then:

docker compose --profile tailscale restart tailscale

Your HTTPS address stopped working after an upgrade

Check that docker compose ps includes tailscale. If it does not, that is why: the address resolves to nothing, which in a browser looks like a blank page with an empty console rather than an error. Tailscale is an optional service, so it only starts when Compose is told to include it — and once it has stopped for any reason, a plain up -d leaves it down.

grep COMPOSE_PROFILES .env || echo 'COMPOSE_PROFILES=tailscale' >> .env
docker compose up -d

That line makes every future up -d include it. Installs created by install.sh have it already. Set it to caddy instead if you use a public domain.

It loads on your phone but not your computer

Check whether another VPN is running on that device. A second VPN client will happily route around Tailscale, and the way it fails is genuinely misleading: Tailscale itself keeps working, so tailscale ping armoryhub still gets replies and everything looks reachable — while connections to port 443 quietly fail. It reads as a broken certificate or a server problem, and you can lose an evening to it. Quickest way to identify it: try the same address from your phone on mobile data. If the phone works and the desktop does not, the problem is the desktop.

You can sign in, but cannot set or enter your PIN

You are reaching the app over plain http:// — almost always by IP address, or via a dashboard's “open app” button. Browsers withhold the cryptography the app needs from insecure origins. Use your https://…ts.net bookmark.

no such service: tailscale or no such service: restore

While the containers are visibly running, this means a dashboard has rewritten your compose file — see CasaOS, Portainer and Dockge.

Anything else

The application checks its own configuration before it serves anything and refuses to start rather than start incorrectly, so almost every failure is explained in its log:

armoryhub logs app

Uninstalling

# Sign out of Tailscale FIRST, or a future reinstall appears as armoryhub-1
docker compose exec tailscale tailscale logout

docker compose --profile tailscale --profile tools down

That stops and removes the containers. Your data is still there, in the data directory next to docker-compose.yml, and docker compose up -d brings it all back. To delete your data as well, remove that directory yourself:

rm -rf ./data

There is no undo, and that includes your backups. Copy anything you want to keep off the machine first.

Got it running?

The beta is free and stays free for you — anything below 1.0 is grandfathered. But if you would rather not use it for nothing, you pick the amount.

No tier, no licence, no receipt to enter, nothing to unlock, and no follow-up. Skipping it is genuinely the expected answer.

Pay what you want

Read it before you run it

The installer, the management helper, the compose file and the Debian bootstrap script are all public, and all of them are linted and validated on every change.