Customer service and sales, answered from what your company already knows, on your own machine.
Point Arlo at your documents and your website. It answers customers on whatever channel they turned up on, cites the passage each answer came from, and hands the conversation to a person the moment it should. Everything it reads and everything it says is stored in a Postgres container on your hardware: nothing is uploaded, and the only thing that leaves is the question being asked of the model.
Same answering engine. Desktop is the whole of it running locally for one business; Arlo Cloud adds the things that only start to matter once a team is running on it, and takes the operations off you.
| Desktop | Cloud | |
|---|---|---|
| Knowledge | ||
| Upload documents, crawl a website, type a note | ✓ | ✓ |
| Scanned PDFs, read with OCR | ✓ | ✓ |
| Keyword search, with no API key at all | ✓ | ✓ |
| Vector search, with an embeddings key | ✓ | ✓ |
| Graph RAG: reaches passages that share no words with the question | – | ✓ |
| Channels | ||
| Web widget | ✓ | ✓ |
| Telegram | ✓ | ✓ |
| ✓ | ✓ | |
| Gmail and Outlook | ✓ | ✓ |
| Answering | ||
| Cited answers, confidence floor, hand-off to a person | ✓ | ✓ |
| Tools: search the knowledge base, search the web, send a file, notify the team | ✓ | ✓ |
| Guardrails you write | ✓ | ✓ |
| Guardrails suggested from your knowledge base | – | ✓ |
| Modes: live, learning, paused | ✓ | ✓ |
| Draft mode: the bot writes, a person edits and sends | – | ✓ |
| Teaching it | ||
| Write the answer to a question yourself, and the files it sends with it | ✓ | ✓ |
| Learns customer details from what the customer said | ✓ | ✓ |
| Learns from your team's replies, automatically | – | ✓ |
| Inbox and contacts | ||
| Every channel in one thread list | ✓ | ✓ |
| Take over, reply as yourself, hand back | ✓ | ✓ |
| Contact cards, one person across every channel | ✓ | ✓ |
| Price list and business calendar the bot can quote | ✓ | ✓ |
| Sales | ||
| Projects: boards, stages, leads | – | ✓ |
| Lead qualification: infers which stage someone belongs in | – | ✓ |
| Deals, bookings, reminders | – | ✓ |
| Running it | ||
| One workspace | ✓ | ✓ |
| Several workspaces, with roles per workspace | – | ✓ |
| Analytics, SSO, audit log | – | ✓ |
| Your own Autobricks key | ✓ | ✓ |
| Arlo's shared model, billed to us | – | ✓ |
| Hosted: backups, and a WhatsApp session somebody else keeps alive | – | ✓ |
| Everything stored on your own machine, in a container you control | ✓ | – |
You need Docker. Nothing else: no Node, no
Postgres, no npm install.
git clone <this repo> arlo-desktop
cd arlo-desktop
chmod +x scripts/setup.sh setup/docker-init.sh
./scripts/setup.sh # macOS, Linux, Git Bash
docker compose up -dOn Windows without a shell, the same thing:
powershell -ExecutionPolicy Bypass -File scripts\setup.ps1
docker compose up -dOpen http://127.0.0.1:9005 and sign in with arlo / arlo.
It runs in the background and keeps running, including after a reboot. The three commands that go with that:
docker compose logs -f app # watch it, Ctrl-C stops watching, not the app
docker compose ps # what is running
docker compose down # stop it; your data staysFirst boot builds the image, so give it a minute before the page answers. Drop the -d to
watch that happen in the terminal instead, then Ctrl-C does stop it.
That is the whole install. setup writes a .env with four freshly generated secrets in
it (three database passwords and the key that encrypts everything else), so there is
nothing to paste and nothing to get subtly wrong. It never overwrites a .env you already
have, so it is safe to run twice.
First boot creates the database roles, the schema and pgvector; the first sign-in creates your workspace, its website widget and somewhere to put knowledge.
Doing it by hand instead
The script is a convenience, not a dependency. cp .env.example .env, open it in any
editor, and fill in four values. There is nothing clever about them: they are secrets
because nobody else knows them, not because of how they were generated.
| Variable | What it is |
|---|---|
POSTGRES_SUPERUSER_PASSWORD |
The Postgres superuser, used once to create the roles |
ARLO_OWNER_PASSWORD |
Owns the schema, runs migrations |
ARLO_APP_PASSWORD |
What the app connects as, and cannot bypass row-level security |
SECRET_KEY |
Encrypts connector tokens and the model key at rest |
Typing on the keyboard is a valid way to fill these in. Mash out twenty-odd characters per line and you are done:
POSTGRES_SUPERUSER_PASSWORD=kj3hf9wm2xqp8rtv6ybn
ARLO_OWNER_PASSWORD=8shdq2mfk4wpx7ncv3jt
ARLO_APP_PASSWORD=p2xn8kqw5jhf3mtb9rvc
SECRET_KEY=whatever you like here, it is hashedTwo rules, and only two:
- The three database passwords must be letters and digits only.
setup/docker-init.shsubstitutes them intosetup/00-roles.sqlwithsed, so a/, a quote, a backslash or an&breaks it. It now refuses rather than mangling, but it is easier not to. Length is up to you; twenty characters is plenty. SECRET_KEYcan be literally any text. It is put through SHA-256 to derive the actual key, so length and character set do not matter, only that it is hard to guess and that you do not change it later. Rotating it makes every stored token read as absent.
Prefer a generator? One of these per value:
head -c 21 /dev/urandom | od -An -tx1 | tr -d ' \n'Two things worth knowing, because both have cost somebody an evening:
- Each
CHANGE-ME-…placeholder appears twice: once on its own line for Docker, once insideDATABASE_URLfor running outside it, and both copies must match. Replace all occurrences, not the first one. - Save the file with Unix (LF) line endings. A
.envwith Windows line endings puts a carriage return on the end of every value, and the password Postgres stores then differs by one invisible character from the one the app sends. What you get ispassword authentication failed for user "arlo_owner"against a file that looks perfect. VS Code shows CRLF/LF in the status bar and switches on click;.gitattributeskeeps a fresh checkout right, and./scripts/setup.shfixes an existing file in place.
arlo / arlo is published in this file, so everybody has it. The app says so on every
page until you change it, in Settings → General, where you can change the username too.
Arlo runs without one: every question just goes to a person, unanswered rather than
invented. To have it answer, paste an Autobricks API key into
Settings → Model provider. It is saved encrypted in the database and written back to
your .env, so it survives docker compose down -v.
Autobricks is the only provider this build talks to. Another one means editing
src/server/llm.ts: there is one endpoint constant and one model default, and that is the
whole of it.
Without an embeddings key, retrieval uses Postgres full-text search: it finds passages that share words with the question. That works, and the test suite runs on it.
For search by meaning, set both of these in .env and restart:
EMBEDDINGS_URL=https://api.autobricksai.com/v1/embeddings
EMBEDDINGS_KEY=<your Autobricks key>
EMBEDDINGS_MODEL=autobricksai/text-embedding-3-smallThe model must return 1536-wide vectors, because that is the vector(1536) column in
migrations/001_schema.sql. text-embedding-3-small is that natively; text-embedding-3-large
and gemini-embedding-001 are 3072 but accept a dimensions parameter, so set
EMBEDDINGS_DIMS=1536 if you use one. Anything already stored is embedded in the background
once a key appears.
This is the one call configured per install rather than per workspace, which is also the one
place another provider can be pointed at: see src/server/embed.ts for why.
Almost everything that goes wrong here goes wrong once, on the first docker compose up -d,
and says so somewhere you were not looking.
dependency failed to start: container …-db-1 is unhealthy
The database container is what failed, so its logs are the ones that say why, and the app's logs, which is where people look, are empty by definition:
docker compose logs dbThe database only runs setup/docker-init.sh on the first boot of an empty volume. That
means two things worth knowing together: a failure there leaves a half-initialised volume
that will not fix itself on restart, and the fix is always to throw the volume away and try
again, not to restart:
docker compose down -v # deletes the database: fine here, it never came up
docker compose up -dLook for one of these in docker compose logs db:
| In the log | What happened |
|---|---|
A database password contains one of / \ ' " & |
Exactly what it says. Those are substituted into SQL and cannot be escaped safely. Use letters and digits. |
sourcing /docker-entrypoint-initdb.d/00-arlo-roles.sh |
The script lost its executable bit: see below. It should say running, not sourcing. |
password authentication failed |
Line endings, usually. Run ./scripts/setup.sh again; it repairs a .env in place. |
zsh: permission denied: ./scripts/setup.sh
The executable bit did not survive however the files got to you: copying a folder between
machines drops it, and so does unzipping on some systems. git clone preserves it.
chmod +x scripts/setup.sh setup/docker-init.shDo both. setup/docker-init.sh matters more than the one you noticed: Postgres sources an
init script it cannot execute instead of running it, which lets that script's set -eu
escape into the entrypoint's own shell and take the rest of initialisation down with it. The
symptom is the unhealthy container above, several steps removed from the cause.
Compose says a variable is missing a value
.env is not filled in. ./scripts/setup.sh does it and now refuses to finish while any of
the four is still blank; "Doing it by hand" in the install section above is the manual
version.
Connectors is where a customer gets a way in.
| Channel | What it needs |
|---|---|
| Web chat | Nothing. Name a site, copy the <script> tag, paste it into your page |
| Telegram | A bot token from @BotFather, and a PUBLIC_URL Telegram can reach |
| A phone to scan a QR code | |
| Gmail / Outlook | An OAuth app of your own: GOOGLE_CLIENT_ID / OUTLOOK_CLIENT_ID and their secrets in .env |
The web widget is the one that works with no third-party account, so it is the one to try first. Allow lists start empty on Telegram and WhatsApp: a linked number answers nobody until you pick who it may talk to. WhatsApp's is per chat, because that socket can see the owner's private conversations.
PUBLIC_URL must be the address a browser actually types. The widget snippet, Telegram
webhook addresses and OAuth redirects are all built from it.
Nothing here requires an API key to work:
| Missing | What happens |
|---|---|
EMBEDDINGS_URL / EMBEDDINGS_KEY |
retrieval falls back to Postgres full-text search |
| An Autobricks key | questions go straight to a person, unanswered rather than invented |
| both | the product still runs, and the whole test suite exercises this path |
Compose is the supported way to run this and the image is what it runs, so putting it on a
VPS or a NAS is the same two commands: what changes is everything in front of it. The port
publishes to 127.0.0.1 deliberately, and there is one account and one password, so a
reverse proxy there is not only for TLS.
docs/DEPLOY.md covers that: the proxy, the headers it has to pass,
PUBLIC_URL, and what to back up.
Arlo Desktop is fair-code, not open source: the Sustainable Use License.
The short version: run it for your own business, free, with no user limit. Read it, change it, build on it, pass it on. What you may not do is sell it or host it as a service for other people, that is what Arlo Cloud is, and it is what pays for this.
If you want to offer Arlo to your own customers, that needs a commercial licence: team@autobricks.ai.
Copyright © 2026 Autobricks AI Pte Ltd.