Deployment¶
This guide covers deploying Ceres as a production service on Linux or macOS.
Overview¶
A typical production Ceres deployment runs on a physical Linux server with:
- A
ceres.yamldefining component drivers that connect to instruments over TCP. - A PostgreSQL (or SQLite) database for persistence.
- A SystemD user service keeping the engine running across reboots.
- An HTTP server providing the web console and REST API.
Prerequisites¶
On the deployment server, you need:
- Python 3.14+
- uv
- PostgreSQL (if using PostgreSQL instead of SQLite)
Project Setup¶
Create a project directory, initialize it, and install Ceres.
Create your ceres.yaml. A production configuration typically looks like this:
service:
name: my-project
server:
http:
port: 8080
authentication:
secret: <generate-a-random-secret>
duration: 30m
database:
type: sqlite
path: ./local/database.sqlite
logging:
output: info
store: debug
events: true
components:
- name: sensor-a
class: my_project.SensorDriver
arguments:
host: 192.0.2.10
port: 2101
output: ./local/data/sensor-a/
- name: sensor-b
class: my_project.SensorDriver
arguments:
host: 192.0.2.11
port: 2101
output: ./local/data/sensor-b/
Paths are relative to the configuration file. A writable database creates the
directories leading to its file, so ./local/database.sqlite works on a fresh project.
Other paths, like a driver's output directory, are the driver's own to create.
Environment Files¶
Every ceres command reads a .env file from the project directory into its
environment before doing anything else, and the engine and its components inherit the
result. Variables already set in the real environment win over the file. The background
service runs from the project directory, so the file applies there the same way.
Use it for values your drivers read from the environment, and keep it out of version control when it carries credentials.
The .ceres Directory¶
Ceres keeps state that belongs to one machine in a .ceres directory next to
ceres.yaml: the address the running engine's CLI server listens on, and the HTTPS
certificate and key that ceres generate certificate writes. The directory carries its
own .gitignore, so nothing in it reaches version control. Leave it out of copies of the
project to other machines too.
Validating Configuration¶
Before starting the service, validate your configuration.
Database Migrations¶
The rust/ceres-database/migrations/ directory is the source of truth for the database schema. Every schema change ships as a migration file named <id>-<name>.sql, or <id>-<name>.sqlite.sql / <id>-<name>.postgres.sql when the SQL differs by backend, rather than as a standalone schema definition.
Whether the engine migrates on its own depends on whether the database is empty, not on which backend it is. An empty database has every migration applied at startup, on SQLite and PostgreSQL alike. A database with data in it is only checked, never migrated, so an upgrade that ships a migration is a deliberate step.
This lists the pending migrations and asks before applying them.
Show every known migration alongside its applied or pending status.
On startup a non-empty database has to already match what the running version of Ceres expects, and the engine refuses to start otherwise. Pending migrations mean you need ceres database migrate. A migration ID the running version does not recognize means the database was migrated by a newer version of Ceres than the one you are starting, which is what a downgrade looks like.
Starting the Service¶
Using ceres service¶
The simplest approach is to use the built-in service management.
This generates a service definition, installs it, and starts the service.
The service is named ceres-<hash>, where the hash is derived from the project directory, so several projects on one machine never collide. Set service.name in ceres.yaml to choose the name yourself.
Linux: a SystemD user service at ~/.config/systemd/user/ceres-<hash>.service. loginctl enable-linger runs automatically, so the service survives logout.
macOS: a LaunchD agent at ~/Library/LaunchAgents/ceres-<hash>.plist.
ceres service status prints the name and the exact path, which is quicker than working out the hash.
Stop and remove the service.
Reviewing the Service File¶
To inspect or customize the generated service file before installing it:
Managing Components¶
Once the service is running, manage components from any terminal.
ceres status # Show engine and component states.
ceres up all # Enable and start all components.
ceres down sensor-a # Disable and stop a specific component.
ceres enable sensor-b # Auto-start on next engine restart.
Applying Configuration Changes¶
After editing ceres.yaml, apply changes without restarting the service.
The engine reconciles the running component tree with the new configuration, creating, updating, or removing components as needed. Running components that were not changed continue without interruption.
Monitoring¶
Web Console¶
With a server.https listener the web console is available at https://<host>:<port>, and with only a server.http listener at http://<host>:<port>. It provides a dashboard for monitoring component state, viewing logs, messages, alerts, and controlling components.
HTTPS¶
With a server.https listener the console is served over TLS, and browsers negotiate HTTP/2, which multiplexes every console request over one connection. Over plain HTTP browsers cap a page at about six connections per host, and each live video widget holds one, so a dashboard with several videos stalls. HTTPS lifts that limit.
The simplest HTTPS setup lets Ceres manage the certificate:
Startup then creates a certificate authority in the project's .ceres/tls directory, once, and issues the HTTPS certificate with it. The certificate names localhost, the loopback addresses, the machine's hostname, and every address of its network interfaces other than loopback and link-local ones, with an ECDSA P-256 key, valid for 365 days. Ceres checks it at startup and daily while running, and issues a new one when it has less than 30 days left, or less than a third of its lifetime for one valid under 90 days, when it lacks a name, or when another authority signed it. The new certificate replaces the old one in the running listener, with no restart and without dropping open connections.
Browsers trust the certificate once they trust the authority, .ceres/tls/ca.crt. Every listener serves it at /ca.crt, the redirecting one included, and the console's login page links to a page with the download, its fingerprint, and steps for each operating system. The authority stays the same through every renewal, so each client trusts it once. It is valid for 10 years and Ceres never renews it. From 30 days before it expires, startup, the daily check, ceres check, and ceres generate certificate warn that it needs replacing.
Name the addresses and DNS names clients reach the server by under auto, in place of the detected ones, along with the lifetime and an authority of your own. A supplied authority has to exist, Ceres never creates one at a path the configuration names. Its certificate has to be a certificate authority allowed to sign certificates, and its key has to belong to it.
server:
https:
certificate:
auto:
ip: [192.0.2.10]
dns: [sensors.example.org]
days: 365
ca:
path: /etc/ceres/ca.crt
key: /etc/ceres/ca.key
days is at most 825, the longest Apple devices accept. Run the same issuance by hand with ceres generate certificate, which prints the authority to trust, the names, the expiry, and the fingerprint. It only writes when a certificate is due, --force reissues it, and --ip, --dns, and --days add names and set the lifetime.
Created the certificate authority .ceres/tls/ca.crt.
Wrote the certificate to .ceres/tls/server.crt.
Wrote the key to .ceres/tls/server.key.
Names: localhost, 127.0.0.1, ::1, sensor-host, 192.0.2.5
Expires: 2027-10-05
SHA-256 fingerprint: F7:65:82:76:E8:92:6A:D7:2B:21:F2:63:DD:72:1A:52:AC:E7:B3:12:A7:B4:FC:49:81:F9:2D:6B:13:2B:08:D0
Trust the certificate authority .ceres/tls/ca.crt on each client to trust the certificate.
Set server.http.redirect to keep the old http:// bookmarks working after a move to HTTPS. The server.http listener then answers every request with a temporary redirect to the same path and query on the HTTPS listener, except /ca.crt, which it serves in place. Its port defaults to 80, and is typically the port the server served plain HTTP on before.
To serve a certificate from elsewhere, like one a public certificate authority issued, leave auto out and name its files with path and key, plus key-password for an encrypted key. Ceres then only reads them. Relative paths resolve against the project directory, and both default to .ceres/tls/server.crt and .ceres/tls/server.key.
Without auto, ceres generate certificate writes the configured paths once, signed by the authority at .ceres/tls/ca.crt, and replaces them only with --force. The certificate is valid for 365 days unless --days sets another lifetime.
ceres check and engine startup fail when the certificate or key cannot be loaded, and name ceres generate certificate and certificate: auto when either file is missing. Without auto, both also warn once fewer than 30 days remain before the certificate expires, ceres check on its output and startup in the engine log. With auto, ceres check reports what startup is about to issue, without writing anything.
CLI Queries¶
Stream logs, alerts, or messages in real-time from the command line.
ceres logs follow # Stream all log entries.
ceres alerts follow # Stream alerts.
ceres messages select --field data --output messages.csv # Export messages.
REST API¶
The HTTP API provides programmatic access to the same data. Generate the OpenAPI schema for reference.
A running server serves its own OpenAPI document at http://<host>:<port>/api/openapi.json, and /api redirects there. Point any OpenAPI client at it, or read the HTTP API reference.
Upgrading Ceres¶
To upgrade to a new version:
If there are database schema changes, run ceres database migrate after upgrading and before starting the service.
Logging¶
Stdout/Stderr Redirection¶
Configure log file paths in the service section.
Log Levels¶
The logging section controls what is printed and what is stored.
logging:
output: info # Minimum level for stdout.
store: debug # Minimum level for database storage.
events: true # Log component lifecycle events.
Per-component overrides are available. See Configuration.
Database Maintenance¶
Pruners¶
Configure automatic record cleanup in ceres.yaml to prevent unbounded database growth.
components:
- name: sensor
class: my_project.SensorDriver
pruners:
- name: clean-messages
prunes: message
schedule: "0 0 * * *"
filter:
max-age: 30d
- name: clean-logs
prunes: log-entry
schedule: "0 0 * * *"
filter:
max-age: 7d
Manual Cleanup¶
Clear all data (preserving schema) if needed.
Database Shell¶
Open an interactive shell for ad-hoc queries.