Deployment

OpenTMS: deployment and tech stack

The whole system runs on one server: in the cloud we run, or in your own infrastructure. Here is what the server needs, how the parts fit together and what they are built with.

In our cloud

We host and run it

We run the server, the updates and the status page; you just sign in. The server and file storage are in the European Union.

On your server

The same stack in your infrastructure

Docker Compose with three images. One script prepares the server, one command deploys a new version, and the same command rolls back to the previous one.

Server requirements

The minimum server is enough to try the system or for one small company. The production column is the server our cloud with many companies is planned for.

WhatMinimumProduction
Processor2 vCPU, x86-648 cores, x86-64
Memory4 GB RAM64 GB RAM
Disk40 GB SSD2 × 512 GB NVMe, RAID 1
Operating systemUbuntu 24.04 or 26.04 LTSUbuntu 24.04 or 26.04 LTS
SoftwareDocker Engine and Compose v2Docker Engine and Compose v2
NetworkPublic IP, ports 80 and 443Public IP, ports 80 and 443, WireGuard
DomainsOffice, driver and API addressesOffice, driver and API addresses
BackupsOn the server's diskAlso off-site: S3 storage or SFTP

The processor must be x86-64 (amd64). 4 GB of memory is enough to build a new version beside the running one, which also needs at least 6 GB of free disk. PostgreSQL's memory settings are computed from the server's RAM.

Architecture

One API, one database and one background worker. Each outside service is reached through its own adapter, and most can be switched off.

WHO CALLSSERVEROUTSIDE SERVICESOffice appdispatcher, headDriver PWAa link, works offlineAI agentsOaura, Claude – propose onlyOther systemsAPI key, webhooksHTTPSCaddyHTTPS, the apps, /api and /mcpAPI /api/v1REST + SSE, 2 instancesMCP serveragent toolsPostgreSQL 18RLS by account_idWorkeroutbox, planned jobs/api/v1, SSE/mcpvia APISQL, RLSoutboxright awayfilesVertex AI (ES)order draftGeoapifyaddresses, mapBrevoemailNoiumdocuments, invoicesRivilėonly through NoiumGPS providersTraccar, Mapon, Wialon, rFMSBackblaze B2 (ES)files, backupsemailsends, checksinvoicespositionsWAL backupssingle entry pointrequestin the background, via the outbox
Everyone – the office app, the driver, AI agents and other systems – calls the same API, so the same permissions and checks apply. The API calls out directly only where a person is waiting for the answer: an order draft, an address search and files. Everything else (Noium, email, SMS, GPS, webhooks) goes through the outbox table and the worker, so an outage of an outside service does not stop the dispatcher's work. Rivilė is reached only through Noium.

Data model

Every row belongs to an account, and the database itself stops anyone from seeing another company's data.

ACCOUNT (ACCOUNTS) · EVERY ROW HAS ACCOUNT_ID, THE RLS BOUNDARYLegal entitylegal_entitiesPartnerpartnersContractcontractsOrderordersTransporttransportsLegtrip_transportsTriptripsTrucks, driversvehicles, driversEventdomain_eventsPrice lineprice_linesStopstopsPlacelocationsInvoice packageinvoice_packagesAccounting linkaccounting_linksDocumentdocuments, document_linksclientsellercarrier, if forwardedbuyer1:N1:N1:N1:NN:11:N stopsaddresspicks up, deliversrevenuecostN:1, locks1:1status from eventsdocument_links
An order is a sale, a transport is one cargo's journey, a trip is one truck or carrier. Transports and trips are joined through legs, so one cargo can travel on several trips and one trip can carry several cargoes. Prices are lines, not columns: revenue on the transport, cost on the trip. Nobody types the status: it is computed from events.

Tech stack

Versions as pinned in the code. No Redis, message broker or search engine: PostgreSQL does those jobs.

Frontend

  • React 19.3 · TypeScript 6.0Office and driver apps
  • Vite 8.3Build and dev server
  • TanStack Query 5.104Server data in the office app
  • AG Grid Community 36.2Order grid
  • Tailwind CSS 3.4 · Radix UIComponents (shadcn/ui)
  • MapLibre GL 6.16Maps
  • Dexie 4.4 · Workbox 7.4Driver app works offline (PWA)
  • openapi-fetch 0.17API client generated from OpenAPI

Backend

  • Python 3.13 · uvOne language for the API, the worker and the CLI
  • FastAPI 0.143 · Uvicorn 0.54REST API and the live stream (SSE)
  • Pydantic 2.14Validation and settings
  • asyncpg 0.32Raw SQL, no ORM
  • WeasyPrint 70PDF documents
  • structlog 26.1JSON logs
  • MCP Python SDK 2.3MCP server for AI assistants
  • Anthropic SDK 1.15Claude on Google Vertex AI, EU region

Database

  • PostgreSQL 18.6The only data store
  • Row-Level SecurityEach company's data separated in the database
  • dbmate 2.36 · squawk 2.70Migrations with up and down, linted
  • pgBackRest 2.59Encrypted backups and WAL archive

Job queue and caching

  • PostgreSQL outboxJobs are written in the same transaction, no Redis or BullMQ
  • FOR UPDATE SKIP LOCKEDPython worker, jobs taken in turn per company
  • LISTEN / NOTIFYLive changes without a separate broker
  • HTTP cache · zstdHashed files cached for a year, API responses no-store

Sign-in and security

  • Argon2 · TOTPPasswords and two-factor sign-in
  • OpenID ConnectSign in with Google and Microsoft
  • OAuthAI assistants signing in to the MCP server
  • AES-256-GCMSecrets stored in the database are encrypted

API

  • OpenAPI 3.1 · REST /api/v1One API for the apps, integrations and agents
  • ScalarPublic API reference at api.opentms.app/docs
  • Standard WebhooksSigned webhooks
  • oasdiff 1.33Blocks breaking API changes

Hosting

  • Docker · Compose v2Three images: backend, edge and database
  • Caddy 2.11HTTPS (Let’s Encrypt), the apps and /api
  • Ubuntu 24.04 / 26.04 LTSnftables and WireGuard, SSH only through the VPN
  • Uptime Kuma 2.5Public status page

Testing and quality

  • pytest 9.1 · HypothesisTests against a real PostgreSQL 18
  • Vitest 5 · Testing LibraryFrontend tests
  • Playwright 1.64The whole system in a browser
  • Locust 2.46Load: 3 companies × 50,000 orders
  • ruff · mypy · ESLint · gitleaksCode and secret checks before every change
Get started

Also fits a carrier with a few trucks

The system goes live in a few days, without a long implementation, training or mandatory modules. You can bring over your existing orders by pasting them from Excel.

  1. 1 · Create an account and a company
  2. 2 · Connect your Noium key
  3. 3 · Invite your colleagues and your first driver

Create an account

Sign up with your work email or a Google or Microsoft account.

Create an accountBook a demo

Already have an account? Sign in