Deployment
Run Spooldoc with Docker or Docker Compose: the image, ports, network to the IBM i, secrets, backups and upgrades.
The container image holds the web app and a Java runtime for jt400, and runs on amd64 and arm64. It needs a Postgres database, three environment variables and a Carbone server to render documents.
Every release publishes the image to ghcr.io/strom-capital/spooldoc,
tagged with its version (0.1.0), its minor line (0.1) and latest.
Pin a version in production so an upgrade happens when you choose it.
Docker Compose
compose.yaml at the repository root starts the published image and a
Postgres 18 database with a named volume:
printf 'BETTER_AUTH_SECRET=%s\nSPOOLDOC_SECRET_KEY=%s\n' "$(openssl rand -base64 32)" "$(openssl rand -base64 32)" > .env
docker compose up -d
Open http://localhost:3000. The .env file next to compose.yaml holds
the settings of apps/web/.env.example; DATABASE_URL is set by the
Compose file. SPOOLDOC_VERSION in .env pins the image version, latest
otherwise. docker compose up --build -d builds the image from the
checkout instead. Set POSTGRES_PASSWORD (URL-safe characters) for anything
but a local trial. The database port is published on this machine only, on
5433, so a local Postgres on 5432 is not in the way.
Docker
With your own database:
docker run -p 3000:3000 \
-e DATABASE_URL=postgres://... \
-e BETTER_AUTH_SECRET="$(openssl rand -base64 32)" \
-e SPOOLDOC_SECRET_KEY="$(openssl rand -base64 32)" \
ghcr.io/strom-capital/spooldoc:<version>
To build the image from source, run docker build -t spooldoc . in the
repository and run spooldoc in place of the published name.
Passing secrets with -e shows them in docker inspect and the shell
history; use --env-file or your orchestrator's secrets. The image runs as
the node user, listens on port 3000 (PORT changes it) and applies the
database migrations when it starts.
Ports
| Port | What it is |
|---|---|
| 3000 | The web app. Publish it, or put a reverse proxy in front of it. PORT changes it in the container; from source, next dev takes the next free port when 3000 is busy. |
| 5433 on the host | Postgres, in the Compose setup, bound to 127.0.0.1 only. |
Behind a reverse proxy
Set APP_URL to the address users type, such as
https://spooldoc.example.com. Origin checks on every write accept that
address and the request's own host, and the session cookie is marked
secure when the address is https. The proxy should pass the Host header
through unchanged.
Network
The container needs a route to the IBM i on the host server ports jt400
uses: 449 (port mapper) and 8470 to 8476 (central, database on 8471 for
JDBC, network print, remote command and sign-on). When Spooldoc runs in
the cloud and the IBM i is on premises, connect them over a private link
such as a site-to-site VPN; do not open the host server ports to the
internet. Set SPOOLDOC_ALLOWED_HOSTS so users can only add the systems
you intend.
Spooldoc connects to:
- the IBM i, on the host server ports above
- its Postgres database
- the Carbone server you configure under Settings, Rendering: the
carboneservice of the Compose file, another server of yours, or Carbone Cloud over HTTPS - the model provider you choose, over HTTPS, or a local model at the base URL you enter, only when the agent is used
Nothing else leaves the host.
More than one instance
Every instance shares the database. Set SPOOLDOC_WATCHER=off on all but
one, so one process reads the data queues and polls; the watcher also
takes database locks, so a second watcher would not double-run a job, but
it would double the IBM i traffic.
Backups
Everything Spooldoc keeps is in the database: servers, jobs and their
versions, captured spooled files, runs and their files, assets and
settings. One pg_dump is the whole backup. Keep SPOOLDOC_SECRET_KEY
with it: a restore under another key cannot read the saved IBM i
passwords and model keys, and they must be entered again. Captured
spooled files and delivered files are in the database in clear text, so
protect the database and its backups as you protect the spooled files
themselves.
Upgrades
Pull or build the new image and start it; the server applies the
migrations. With Compose, set the new SPOOLDOC_VERSION (or leave it at
latest) and run docker compose pull && docker compose up -d. Take a
pg_dump first. What changed in each release is in the
release notes. Until
1.0, a minor release (0.1 to 0.2) can include breaking changes, so read
them before you upgrade, and pin X.Y.Z rather than following latest.
From 0.2.0
The migrations turn each job's template and formats into the job's first template, Template 1, so every job and version opens again. Two things need you:
- Spooldoc now renders with Carbone. Run it next to Spooldoc and connect it in Settings; see Carbone.
- Templates and file name patterns are now written with Carbone tags,
not Nunjucks. Rewrite
{{ doc.invoiceNo }}as{d.pg.invoiceNo}. The Template tab of each job lists the Nunjucks left in its template, and the definition check names a file name pattern that still has it. Until then, a run of the job fails with the same message. Save the job as a new version once both are rewritten.
Sizing
A run reads one spooled file and extracts in the Node process, so memory
goes with the size of the reports. Carbone renders: an HTML to PDF render
takes tens of milliseconds, and the Carbone container uses about 500 MB of
memory. Its full image is about 560 MB to download. UV_THREADPOOL_SIZE is set to 16 in the image because
jt400 calls block a thread pool thread.