$ aoox

DocsApplications

Creating an application

Connect a Git repo or a ready-made image to a project and control how its container is run.

Time
± 3 minutes + build duration
Needs
A local registry (Git source) or an image
Result
A running container from the branch you chose

Prerequisites

  • For applications from a Git repo: the local registry is already provisioned — built images get pushed there. Without it, the first deploy fails.
  • You already have a project for this application to live in.
  • For private repos: a Git credential has already been added in Settings.
  • The application listens on a fixed port and accepts connections from 0.0.0.0, not just localhost.

Two application sources

SourceWhat you fill inDeploy flow
Git repositoryRepo URL, branch, credential (if private), build methodClone → build (Dockerfile / Nixpacks / static) → push to registry → run.
Ready-made image (pulled)An image reference (ghcr.io/acme/api:1.4) and, if private, the external registry holding its credentialsNo build: the image is pulled then run. Supports automatic updates when the tag's digest changes.

The local registry is only required for a Git source (where built images are stored). Applications from an image don't need it.

Steps

  1. On the project page, click New application

    Fill in the form. Most of it can be changed later in the Settings tab.

    FieldDescription
    NameBecomes a unique slug (app_name); container name = aoox-app-<slug>, shown (with a copy button) in the application header; other apps on the same server reach it at http://aoox-app-<slug>:<container port>.
    Git repositoryThe repo's https URL. Without a user:token in the URL — use the Git credential select for private repos.
    BranchThe branch that gets built. The webhook only reacts to pushes on this branch.
    Git credentialNone (public repo) or one of your stored credentials.
    Build methodDockerfile or Nixpacks — see How builds work.
    Container portThe port the application listens on inside the container (e.g. 3000).
    Host portOptional. Fill it in for IP:port access without a domain. Leave it empty when using a domain — blue/green requires an empty host port.
    Listens onOnly with a host port. Public (default for applications) publishes it on every interface; This server only (127.0.0.1) makes it reachable from the server itself or an SSH tunnel. Docker-published ports bypass ufw, so use the provider firewall for public ones (see Start from a fresh VPS). Changing it recreates a running application like a new host port does. Not available for Swarm services.
    Health check pathOptional but recommended, e.g. /health. Determines deploy success/failure and enables blue/green.
    ServerThe aoox host (default) or a remote server. Only shown if a server is registered.
    Deploy modecontainer (default) or service with replicas when Swarm is active — plus node placement and rolling-update settings.
    Auto updateImage sources only: periodically checks the tag's digest and deploys when it changes.
    CPU / memory limitsOptional. See Monitoring.
    Example — a Next.js application in a public repo
    Name
    shop
    Git repository
    https://github.com/acme/shop
    Branch
    main
    Git credential
    None (public repo)
    Build method
    Nixpacks
    Container port
    3000
    Host port
    (empty — using a domain)
    Health check path
    /health
  2. Choose Access

    Before saving, the form asks how this application will be reachable — so a freshly created application doesn't silently end up unreachable once the deploy finishes.

    OptionWhat happens
    IP + portA host port is assigned automatically (checked for conflicts against other applications/databases/compose stacks/containers before saving) — immediately reachable at http://<server-ip>:<port>.
    DomainA host + HTTPS toggle is added right now, instead of later from the Domain tab. If the reverse proxy hasn't been provisioned yet, it's started automatically — the same behavior as setting the panel domain, see Domain for the panel → Via the dashboard.
    Set up laterNo port or domain yet. The application still gets deployed, but isn't reachable until one is added later from the Settings or Domain tab.
  3. Save, then fill in environment variables (if needed)

    The Environment tab → env editor, one KEY=VALUE per line. Values are applied when the container is created, not at build time — see Environment variables.

  4. Open the Deploy tab and click Deploy

    What happens, visible in the realtime log:

    queued    deployment created
    building  Docker daemon clones the repo → builds the image
    pushing   image → local registry   (skipped on a remote server)
    starting  new container created, waiting for healthy
    success   old container replaced · application status Running

    The next deploy is rejected (409) while a deployment is still active. A failed build/push doesn't touch the running container. Details in Deploy & rollback.

Verification

  • The first deployment ends in success and the application's status badge reads Running.
  • The container log (Deploy tab) shows the application's output, e.g. Listening on :3000.
  • If using a host port: curl http://<server-ip>:<host-port>/ responds. If using a domain: continue to Proxy & domain.

Tabs on the application page

TabContains
DeployDeploy/Stop/Start buttons, deployment list, deployment & streaming container logs, metrics, Rollback.
SettingsThe same form as creation, plus build args (environment has its own tab). Host port, domains, and resource limits apply immediately to a running application — the container is recreated from the same image without a build (with a host port there is a brief downtime, since two containers cannot bind the same port). A stopped application picks up its new port when started again, and a host port change is rejected (409) while a deployment is active. Other settings take effect on the next deploy. Also holds the Maintenance mode card.
DomainHostname for the application + HTTPS toggle + DNS check.
MountVolumes, binds, files — and volume backups.
EnvironmentThe application's env variables, saved on their own, plus the secret source (Infisical). Also the Variable sets card: the ordered set environments this application links, which ${{vars.KEY}} resolves from (see Variables). Applies on the next deploy.
JobsScheduled commands.
WebhookWebhook URL, secret, and pull request previews.
ConsoleInteractive shell straight into the running container.

Console

The Console tab opens a docker exec -it into the application's running container, right from the dashboard — different from the Terminal page, which opens a shell on the host over SSH. Handy for inspecting files, running a debug command, or checking the environment without needing SSH access to the server.

  • Access: project role developer and up (including platform admin/owner) — a viewer never sees this tab, and the API rejects it too if tried directly. A read-only API token is rejected as well.
  • Logged in the audit log — every console session opened is its own row in Settings → Audit log, with who and when.
  • service mode (Swarm) with more than one replica: pick a task from the dropdown above the terminal; only tasks running on this node are reachable.
  • Shell used: bash if the image has it, otherwise sh. A distroless/shell-less image shows Docker's own error message and the session ends right away — not a bug, there simply is no shell to open.
  • Changes made in this shell are temporary: files edited through Console are lost on the next redeploy, unless they live on a mounted volume.

Log error detection

Every minute, the API scans the container log of every Running application for common error patterns (traceback, panic, exception, level=error, and similar) and sends an Application error notification when it finds one — see Notifications for the pattern list, cooldown, and how to turn it on (off by default, per channel).

For an application whose normal output just happens to look like errors (e.g. a verbose framework), turn on Ignore error logs on the Settings tab — that application is skipped from the scan entirely, not just muted.

Maintenance mode

Show visitors a maintenance page instead of the application, without stopping it. It is the first card on the Settings tab, called Maintenance mode.

  • The Enabled switch applies immediately and does not restart or recreate the container: only Traefik's routing changes. While it is on, the status strip shows a Maintenance badge.
  • Every request to every domain of the application gets the page with HTTP 503 and Retry-After: 300 (5 minutes), so search engines and monitors see a temporary outage. This includes HTTPS domains with a custom certificate, and your own middlewares still run in front of it.
  • Page message: plain text, up to 280 characters, with its own Save message button; leave it empty for the default text. It is always shown as text, never as HTML. The page itself is in English.
  • A deploy while it is on keeps the page up. A domain you add or remove while it is on is covered or released automatically. Switching it off brings the application back at once.
  • Developers and up can switch it; viewers see the card read-only.
What it does not cover
Preview deployments keep serving, and so does direct access through the Host port (IP:port): only the domains are put in maintenance.
  • Applications on a remote server are not supported yet: the card says so and the API refuses it (400).
  • You cannot move an application to another server while it is in maintenance (409); switch it off first.
  • Project export/import does not carry the flag, so an imported application never starts in maintenance.

Common pitfalls

SymptomCause & fix
Monorepo: build cannot find package.json / DockerfileSet Root directory (for example apps/web) in the Git part. See Build types.
Deploy fails at the pushing stageThe local registry isn't provisioned yet, or REGISTRY_PUBLIC_HOST isn't localhost without TLS.
Build fails: repository not foundA private repo without a Git credential, or the token lacks read access.
Pull fails: unauthorized (image source)A private image with no external registry selected, or an expired registry token.
Health check never turns healthyThe container port doesn't match the port the app listens on, the app only listens on 127.0.0.1, the path doesn't return 2xx, or the image lacks wget/curl/node/python3.
Running but the domain 404sThe container isn't healthy yet, or DNS isn't pointed at it yet — use the DNS check button on the Domain tab.
New env doesn't take effectEnv is applied when the container is created — deploy (or rollback) once more.
Remote servers have limitations
Applications on a remote server are built on that server's daemon, without pushing to the registry and without a domain/Traefik — access is via host port. Details in Remote servers.

Next steps

Found a mistake? Edit this page on GitHub ↗