Run with Docker
This page covers how an installation runs once it exists: its folder, the commands that control it, and updates.
Before you begin
An installation is a handful of Docker containers that work together. Docker Compose starts, stops and updates them as one unit, the stack:
| Container | What it does |
|---|---|
noticeos, the app | Serves the Tower, receives the reports sites send (the data receiver), runs the scheduled jobs (the scheduler) and reads and writes tasks (the task client) |
postgres | The store: every report, reading, ledger row and setting |
dolt | The task database: every project's tasks |
backup | Optional: takes the nightly backup. See Backups and restore |
The app runs from one read-only image and never creates or migrates a database.
The stack has one definition for every installation, deploy/compose/stack.yaml. An installation is a folder that definition reads: one Docker command or pnpm os:setup makes a new one, and pnpm os:convert-legacy moves a legacy one into it. Nobody writes a Compose file.
An installation set up with Docker alone has no checkout. Its folder keeps a copy of the definition, and its noticeos launcher runs each command: where this page shows pnpm os:apply -- --confirm apply, run ~/noticeos/noticeos apply --confirm apply.
The installation folder
| In the folder | What it holds |
|---|---|
installation.env | This installation's choices, then the layout derived from the folder |
state/ | The app's state, mounted at /state: saved settings, bootstrap secrets, logs, the task client |
spokes/ | Task repositories, mounted at /spokes |
postgres-secrets/, dolt-secrets/ | The database logins |
backup/ | The backup worker's settings, its working folders, exporter checkouts and the offsite handoff |
noticeos | Without a checkout: the launcher every command runs through |
stack.json | Without a checkout: the selector that points the commands at this folder |
stack/ | Without a checkout: the stack definition, copied from the image the app runs |
The choices in installation.env are the project name, the app image, the app's user, the Tower's address, the three ports, the hostnames the Tower may be opened on, the two data volumes, and the folders an installation may keep elsewhere: task repositories, exporter checkouts and the offsite folder. Every other path is derived from the folder. The checkout's .local/stack.json names the stack definition and this file; os:setup and os:convert-legacy write it. Without a checkout, the folder's stack.json does. pnpm os:status prints the folder on its first line.
Three ports are published outside the stack's network: the Tower's, and Postgres and the task database for the operator's tools. All three listen on 127.0.0.1; only the Tower's can be opened to your network. Provider credentials are connected in the Tower, not in files.
Change a setting
Edit
installation.envin the installation folder.In the checkout, read the plan:
shpnpm os:applyWithout a checkout, run
~/noticeos/noticeos apply. It lists each change (a port, a setting such asTOWER_ALLOWED_HOSTS, the app's user, a folder) and the services it recreates.Type
applyto apply it. Without a terminal, runpnpm os:apply -- --confirm apply(~/noticeos/noticeos apply --confirm apply).
Only the services whose settings changed are recreated: the databases first, with the app and backup worker stopped meanwhile, then the backup worker, then the app. Each waits for health. If one does not become healthy, the stack returns to the settings it last ran healthy with. installation.env keeps your edit, so you can fix it and apply again.
Apply stops without changing anything when applying the edit would point a database at other data, rename the project, bind a port that is already in use, or create a missing container. After a pull that changes the stack definition, os:apply applies that too. With nothing to apply, it prints Nothing to apply and records the current settings as the ones to return to.
pnpm os:status prints a settings: line while an edit waits to be applied. pnpm os:restart applies nothing.
Open the Tower to your network
No login
Standalone has no user login: anyone who reaches the Tower can read and change your data. By default it listens on 127.0.0.1, this machine only.
To reach it from other devices, set NOTICEOS_TOWER_BIND in installation.env to 0.0.0.0 (every device on the network) or to one IPv4 address of this machine, then run pnpm os:apply. Do it only on a network you trust, or behind a VPN or an access proxy. Every device that reaches an open Tower can change your tasks, through its pages or its MCP address (Connect an agent).
The plan prints a warning before it opens the Tower. Devices open it by IP address, at the port NOTICEOS_TOWER_PORT names. To use a machine name instead, add it to TOWER_ALLOWED_HOSTS: the app runs in a container, so it cannot read this machine's name. Keep that allowlist; never allow every host. Without a NOTICEOS_TOWER_BIND line in installation.env, the Tower listens on 127.0.0.1.
Convert a legacy installation
An installation that runs from Compose files of its own converts into the folder layout in one command. Its data volumes, image, ports, the Tower's address and secrets stay the same.
In the checkout whose
.local/stack.jsonselects the installation, read the plan:shpnpm os:convert-legacyIt lists what it copies into the new folder (
~/noticeosunless you add-- --dir <folder>), what stays where it is, and every difference that remains: a file from this checkout that differs from the one mounted now, a mount the layout has no place for, a changed port binding.Type
convertto apply it.
The stack stops while its folders are copied, then starts from the new folder; .local/stack.json changes only once it is healthy. Task repositories, exporter checkouts and the offsite folder stay where they are unless they are inside the checkout the old Compose files came from. The old files are never changed. If the new stack does not become healthy, the old one starts again automatically. To go back later, run pnpm os:convert-legacy -- --undo; what the app wrote to its state folder since then stays in the new folder.
An app that runs the checkout's live source (pnpm os:dev) converts too, even while it is broken: the plan starts from the prepared stack os:dev recorded, the app returns to that image for the move, and it runs the checkout's live source again from the new folder afterwards.
Control the stack
From the checkout, run the stack with the os:* commands:
pnpm os:status
pnpm os:start
pnpm os:stop
pnpm os:restart
pnpm os:logsWithout a checkout, run the same commands through the folder's launcher:
~/noticeos/noticeos status
~/noticeos/noticeos restart
~/noticeos/noticeos logs --lines 200Add -- --config /absolute/stack.json to act on another installation's selector. The commands require existing noticeos, postgres and dolt containers, plus backup when present. They never create containers, pull images, apply migrations or remove volumes. An edit to installation.env takes effect with pnpm os:apply.
Start brings up the databases, then the backup worker, then the app. Stop reverses that order. Restart waits for health at each step and stops at the first failure. Daily operations covers the rest.
Update the app from main
An installation without a checkout updates with ~/noticeos/noticeos update, which moves the image and the stack definition together; see Install with Docker. The rest of this section is for a checkout.
Committing to main changes the source only; it does not change what is running. pnpm os:restart restarts the same image. To run new code:
In the checkout, check what runs and whether
maindiffers:shpnpm os:statusFrom a clean checkout at
main, prepare the image and a plan. Services keep running:shpnpm os:updateReview the printed plan, then type
updateto apply it.
The image is the one CI published for main's commit, used only when it was built from exactly this checkout's source. When none is published yet (it appears a few minutes after CI passes on main) or the registry is unreachable, the update builds it here and prints the reason. pnpm os:update -- --build always builds here. A fork's commits are not published, so a fork builds here.
Apply recreates only the noticeos container and waits up to 90 seconds for health. It applies no migration, and it stops before fetching or building when main has a migration the database does not have; see Upgrade. If the app is not healthy within that time, it restores the previous image automatically. The Wall reloads on its next request; the other Tower pages show an update prompt so that drafts stay open.
To go back to the previous image, run pnpm os:rollback and type rollback. An image built before revision labels existed needs -- --baseline-commit COMMIT on the first update.
Every service restarts unless stopped; for startup after a reboot, enable your Docker engine's own startup setting.
Run live source during development
For a local installation where you want edits to appear at once:
In the checkout, run the app from its live source:
shpnpm os:devTo return to the prepared image, run:
shpnpm os:prod
The app mounts the checkout read-only and refreshes the Tower live. The Wall caption shows DEV. Scheduler changes need pnpm os:restart; UI and Worker edits refresh on save. os:update stops without changing anything while the app runs live source.
Verify
pnpm os:statusprints each service's state and health, the running source, and ends withupdate: currentwhen the app runsmain's commit.- The Tower opens on
127.0.0.1at the portNOTICEOS_TOWER_PORTnames ininstallation.env, or on the addressNOTICEOS_TOWER_BINDnames. - After
pnpm os:apply, it printsApplied:andpnpm os:statusprints nosettings:line. - After an update, the app is healthy within 90 seconds and the status shows the new commit.
If it didn't work
- An
os:*command reports that the selector is invalid or a service is unexpected:.local/stack.jsonis missing or names files that moved.pnpm os:setuporpnpm os:convert-legacywrites it; the stack must containnoticeos,postgresanddolt, and at mostbackupbesides. pnpm os:convert-legacystops: the error message names what the layout cannot hold (another service, a stack that is not healthy, a network of the same name it did not create). Nothing was changed.pnpm os:applystops: the error message gives the reason, and nothing was changed. See Whenpnpm os:applystops.pnpm os:applyreports that starting a service failed: the message shows whether the stack returned to its earlier settings. Fixinstallation.envwith Docker's output andpnpm os:logs -- <service>, then apply again.os:updateoros:setupreports that the published image "was not built from this checkout's source": nothing was changed. Add-- --buildto build it here.os:updatestops on changed source, declarations or image labels: the stack changed between preparing and applying the plan. Runpnpm os:status, then update again.- The development app stops after a pull: the checkout's packages changed since its image was built, and
pnpm os:logs -- noticeosshows this. Runpnpm os:dev; it builds a matching image and moves the app onto it. os:restartoros:updatenames a service that is not healthy:pnpm os:logs -- <service>shows why.
More symptoms are in Troubleshooting. The full guide, including the backup worker and running report scripts inside the container, is on GitHub: https://github.com/notice-cx/NoticeOS/blob/main/deploy/compose/README.md.