$ aoox

DocsStart here

Installation

Run aoox on your own server with Docker Compose, then create the first owner account.

Time
± 5 minutes
Needs
A Linux VPS + Docker
Result
Dashboard on :3000, an owner account
Brand-new server? Start with the fresh VPS guide: SSH, a sudo user, firewall, then this install, all the way to a domain with HTTPS.

Fastest path: one command

For a fresh Linux VPS with nothing installed yet — installs Docker (if it's missing), generates every secret, and starts the stack. No Node.js or git clone needed first.

curl -fsSL https://aoox.dev/install.sh | sudo sh

With a domain + automatic HTTPS, and creating the owner right away:

curl -fsSL https://aoox.dev/install.sh \
  | sudo WEB_DOMAIN=panel.example.com API_DOMAIN=api.panel.example.com \
    ACME_EMAIL=you@example.com \
    ADMIN_EMAIL=you@example.com ADMIN_PASSWORD='a-strong-password' \
    sh
Needs sudo (or being logged in as root) — the script installs Docker and writes to /opt/aoox. Forgetting it fails with must be run as root.
Terminal SSH user. A new install, through this one-line script or sudo aoox install, fills TERMINAL_SSH_USER with the name of the user who ran sudo (run directly as root, or when that name is not a plain login, it stays root). A new install also authorizes the terminal key automatically (creates the key in secrets/ and adds it to that user's ~/.ssh/authorized_keys); skip it with AOOX_NO_TERMINAL_AUTHORIZE=1 before sh (or --no-terminal-authorize on the CLI). This step refuses a symlinked ~/.ssh or authorized_keys; a problem here only prints a warning and the install continues. An explicit value wins: put TERMINAL_SSH_USER=ubuntu before sh on the script, or --terminal-ssh-user on the CLI. Existing installs don't change — change it in Infrastructure → Environment. On a cloud VPS root login is usually off, so this user is the one to use; details in Web terminal.
This script is served from aoox's own domain (not a third party) and pulls the compose files straight from the aoox-cli repo — read the source first at public/install.sh if you'd like. Want confirmation prompts and flags instead of env vars? Use aoox install from the CLI — same logic, just a matter of preference. The difference from the script above: aoox install needs the CLI itself already installed on that server first, while install.sh above needs nothing but curl and sh. Installing the CLI itself doesn't need Node.js either — see installing the CLI without Node.js (curl -fsSL https://aoox.dev/install-cli.sh | sh).

Manual: prerequisites

The steps below are useful if you'd rather see or control every part yourself.

  • A Linux server with Docker 24+ and Compose v2. Check with: docker compose version.
  • Ports 3000 (web) and 3001 (API) open in the firewall for your browser. Ports 80/443 must be free if you plan to use a domain later.
  • Access to the Docker socket (/var/run/docker.sock) — the API uses it to build images and run containers.
Minimum specs
For a server that runs the panel: 1 vCPU and 2 GB RAM plus swap at the very least (2 GB with swap is the tested minimum), 4 GB or more recommended, and advised for Railpack builds or several databases (see Choosing a VPS and adding swap). The idle panel stack (PostgreSQL, API, web, proxy and registry) already takes roughly 600-900 MB before any application, and image builds on the server need 1-2 GB more. 1 GB is only enough for a remote server (Infrastructure → Remote servers) that runs applications without the panel; add swap there too. Actual requirements depend on the applications and databases you deploy; the first Nixpacks build needs ± 1 GB of extra disk space for the base image.

Manual: step by step

  1. Clone the API repo and copy the env file

    The distribution compose file and the example env live in the aoox-api repo.

    git clone https://github.com/hideandseeklab/aoox-api.git
    cd aoox-api
    cp .env.dist.example .env.dist
  2. Generate secrets and find the Docker GID

    Run these on the server, then copy the results into the next step:

    Values you'll need
    openssl rand -hex 32                  # JWT_SECRET
    openssl rand -hex 32                  # ENCRYPTION_KEY
    stat -c %g /var/run/docker.sock       # DOCKER_GID (usually 999 or 0)
  3. Fill in .env.dist

    .env.dist (minimal)
    POSTGRES_PASSWORD=replace-with-a-strong-password
    JWT_SECRET=<result of openssl #1>
    ENCRYPTION_KEY=<result of openssl #2>
    DOCKER_GID=<result of stat>
    
    # the URL typed in the browser — must match exactly
    WEB_ORIGIN=http://192.168.1.10:3000
    PUBLIC_API_URL=http://192.168.1.10:3001
    VariableDescription
    POSTGRES_PASSWORDPassword for aoox's internal database.
    JWT_SECRETSecret used for session tokens. Changing it logs everyone out.
    ENCRYPTION_KEYEncryption key for stored credentials (registry, Git, notifications, S3). Never change it once there's data — old credentials become unreadable.
    DOCKER_GIDThe Docker socket's GID, so the API container can use the host's Docker.
    WEB_ORIGIN / PUBLIC_API_URLThe web & API URLs as seen from the browser. The session cookie and terminal gateway check this origin — a different scheme/host/port is rejected.
  4. Start the stack

    docker compose -f docker-compose.dist.yml --env-file .env.dist up -d

    Images are pulled from Docker Hub (hideandseeklab/aoox-api and hideandseeklab/aoox-web). Database migrations run automatically the first time the API container boots.

  5. Create the owner account

    Open WEB_ORIGIN in your browser. While the users table is still empty you're redirected to /setup — enter a name, email, and password for the first owner account.

    Without interaction (optional)

    Set ADMIN_EMAIL, ADMIN_PASSWORD, and ADMIN_NAME in .env.dist before running up -d. These values are only used while there are no users yet and never overwrite an existing account.

Verification

On the server
docker compose -f docker-compose.dist.yml --env-file .env.dist ps
# api, web, postgres → running

curl -s http://localhost:3001/auth/setup-status
# {"needsSetup":true}  before the owner account is created
# {"needsSetup":false} afterward
  • After logging in, the Dashboard shows up and the Settings menu opens without an error.
  • The Terminal menu may show SSH instructions — that's normal before the terminal is configured.

Next steps

Upgrading

The fastest way (needs INSTALL_DIR set, same as Domain panel): Settings → Update aoox → Check for updates → Apply. Or from the CLI:

Clicking Apply update shows a confirmation dialog first (the panel restart usually takes 1–3 minutes), then the card switches into an “Applying” mode — buttons disabled — and the dashboard checks on its own, periodically, until the panel is back on the new version, then reloads the page automatically. A connection drop while waiting (because the api/web containers are being restarted) is expected and isn't shown as an error. If the panel still isn't back after 5 minutes, the card shows manual recovery steps over SSH.

When a newer aoox version has been published, the owner sees an Update available button, with the version number, at the bottom of the sidebar — click it to open the Update aoox page. Other roles never see it. The server looks up the newest version on Docker Hub by itself (once after the panel starts, then every few hours); with no internet access the button simply doesn't appear and nothing errors. The button only shows for installs that follow the :latest tag — if the image is pinned to another tag, the Update aoox page only lists the newest version for information.

aoox update panel            # check only
aoox update panel --apply    # apply if an update is available

Manual, over SSH, if INSTALL_DIR isn't set:

docker compose -f docker-compose.dist.yml --env-file .env.dist pull
docker compose -f docker-compose.dist.yml --env-file .env.dist up -d

Database migrations run automatically when the API boots. To pin a version, set API_IMAGE and WEB_IMAGE in .env.dist, e.g. hideandseeklab/aoox-api:0.1.0-rc.1 (see the available tags on Docker Hub) — both paths above read the same variables. An install pinned to a tag never sees the “Update available” button in the sidebar; the Update aoox page still lists the newest version for information. The panel recognizes the installed tag from the API_IMAGE variable, which the compose file now forwards to the API container (older installs: run aoox reinstall so the compose file is refreshed too).

On a pinned install the Update aoox page disables Apply update and explains why: pulling the same tag again would not change the version, and aoox update panel --apply / the API answer with a refusal instead of reporting success. To move to a newer version, set API_IMAGE and WEB_IMAGE to the new tag (or remove both lines to follow :latest), then run sudo aoox reinstall. The one exception is a pinned tag that was republished under the same name: that can still be applied.

Important limitation: both aoox update panel and the “Apply update” button only run pull + up -d against the docker-compose.dist.yml that is already on the server — neither one ever rewrites that compose file itself. If a release adds a new variable to the compose file (for example a new env var that needs to reach a service), an existing install won't pick it up through an update — and neither do new variables in .env.dist (for example Terminal failing with TERMINAL_SSH_USER is not set after an update). The easiest fix that is safe for your data: sudo aoox reinstall — it rewrites the compose file from the newest copy, merges .env.dist (secrets are never regenerated), then pulls and recreates without touching volumes. Prefer reinstall over aoox install --new-secrets (which only exists together with --wipe-data and deletes the old data). The manual way: edit docker-compose.dist.yml in INSTALL_DIR (default /opt/aoox) per the release notes, then:

docker compose -f docker-compose.dist.yml -f docker-compose.override.yml --env-file .env.dist up -d

(-f docker-compose.override.yml only if that file exists — it's created automatically when you set a Domain panel from the dashboard).

Building from source

Check out aoox-api and aoox-web side by side, then add the build compose file:

docker compose -f docker-compose.dist.yml -f docker-compose.build.yml --env-file .env.dist up -d --build

Uninstalling

docker compose -f docker-compose.dist.yml --env-file .env.dist down -v
Be careful
-v deletes aoox's own data volume (users, projects, deployment history). Application, database, registry, and proxy containers created from the dashboard are not removed along with it — remove them from the dashboard first, or via docker with the label filter com.docker.compose.project=aoox.

Found a mistake? Edit this page on GitHub ↗