The stack#
Run one container for each responsibility:
| Container | Purpose |
|---|---|
postgres | Database with the extensions Dicto requires |
db-migrator | One-shot database migration before the server starts |
server | Dicto API |
rustfs | Recording and file storage |
management | Browser-based administration |
The migration container uses the same release image as the server, with migrate as its command. It exits after successful migration; the server waits for that successful exit before starting. Prometheus can be added separately.
1. Download the files#
Save these files together in your deployment directory:
- compose.yaml
- server.yaml
-
secrets.env.example, renamed to
secrets.env - postgres.Dockerfile
- server.schema.json
The PostgreSQL build file installs pgvector and pg_textsearch, which are required by the database migrations. It builds PostgreSQL 18 with a persistent named volume.
The example uses the default GHCR images with :latest. Authenticate to GHCR if those images are private.
2. Configure your installation#
In secrets.env, set the public API URL and replace every credential placeholder. Generate a URL-safe database password, for example with
openssl rand -hex 24; Compose uses it for both PostgreSQL and the server connection string. Keep the auth secret stable across restarts.
chmod 600 secrets.env
In server.yaml, change the public auth URL, trusted browser origins, transcription endpoint and model name for your organization. Add an end-user web app's origin and callback if you also host that client. The management app's
API_URL must be reachable from users' browsers.
Recording storage points to http://rustfs:9000 inside the container network. allow_http: true
permits that internal connection; expose the public API and management app through your HTTPS reverse proxy.
3. Define the containers#
The downloaded compose.yaml contains this configuration:
services:
postgres:
build:
context: .
dockerfile: postgres.Dockerfile
restart: unless-stopped
environment:
POSTGRES_DB: dicto
POSTGRES_USER: dicto
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dicto -d dicto"]
interval: 5s
timeout: 5s
retries: 20
rustfs:
image: rustfs/rustfs:latest
restart: unless-stopped
environment:
RUSTFS_ACCESS_KEY: ${STORAGE_ACCESS_KEY_ID}
RUSTFS_SECRET_KEY: ${STORAGE_SECRET_ACCESS_KEY}
RUSTFS_CONSOLE_ENABLE: "true"
volumes:
- rustfs-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/health"]
interval: 5s
timeout: 5s
retries: 20
db-migrator:
image: ghcr.io/truhnlab/dicto-server:latest
command: ["migrate"]
restart: "no"
env_file: secrets.env
environment:
DICTO_CONFIG_FILE: /etc/dicto/server.yaml
DB_URL: postgresql://dicto:${DB_PASSWORD}@postgres:5432/dicto
volumes:
- ./server.yaml:/etc/dicto/server.yaml:ro
depends_on:
postgres:
condition: service_healthy
server:
image: ghcr.io/truhnlab/dicto-server:latest
command: ["serve"]
restart: unless-stopped
env_file: secrets.env
environment:
DICTO_CONFIG_FILE: /etc/dicto/server.yaml
DB_URL: postgresql://dicto:${DB_PASSWORD}@postgres:5432/dicto
volumes:
- ./server.yaml:/etc/dicto/server.yaml:ro
depends_on:
db-migrator:
condition: service_completed_successfully
rustfs:
condition: service_healthy
management:
image: ghcr.io/truhnlab/dicto-management-app:latest
restart: unless-stopped
environment:
API_URL: ${API_URL}
BASE_HREF: /
depends_on:
server:
condition: service_started
volumes:
postgres-data:
rustfs-data:
Database and object storage data live in named volumes. The containers communicate over the stack's Docker network. Attach your reverse proxy to that same network and route the API and management hostnames to the corresponding container and listening port.
Listening ports#
| Service | Container address | Purpose |
|---|---|---|
| Dicto server | server:3000 |
Public API through your HTTPS reverse proxy |
| Management app | management:80 |
Administration interface through your HTTPS reverse proxy |
| PostgreSQL | postgres:5432 | Internal database connection |
| RustFS S3 API | rustfs:9000 | Internal recording and file storage |
| RustFS console | rustfs:9001 | Storage administration |
| Database migrator | None | Runs migrations and exits |
| Dicto monitoring, when enabled | server:9091 |
Internal metrics endpoint |
| Prometheus, when added | prometheus:9090 | Monitoring interface |
The server listens on 0.0.0.0:3000, as configured in server.yaml. These are container listening ports; the example uses the shared Docker network for access rather than publishing ports on the host.
4. Start database and storage#
docker compose --env-file secrets.env up --build -d postgres rustfs
Route an administrator-only hostname through your reverse proxy to rustfs:9001 and open the RustFS console there. Sign in with the configured storage access key and secret, and create the private
recordings bucket. The named volume persists its contents. See the RustFS container guide
for console and storage administration.
5. Validate and start Dicto#
docker compose --env-file secrets.env run --rm --no-deps db-migrator validate-config
docker compose --env-file secrets.env run --rm --no-deps db-migrator validate-config --resolve-env
docker compose --env-file secrets.env up --build -d
The final command runs the migration container and starts the server only if migrations succeed. Validation checks configuration structure and values; it does not test inference credentials or storage permissions.
The example temporarily enables admin seeding for an empty database. Set ADMIN_EMAIL and
ADMIN_PASSWORD before first startup. After signing in successfully, replace the complete
seed section with the following, remove the bootstrap credentials from secrets.env, and recreate the server:
seed:
admin:
enabled: false
docker compose --env-file secrets.env up -d --force-recreate server
The seeder skips creation when users already exist. It is not a password-reset mechanism.
6. Verify the installation#
docker compose --env-file secrets.env ps --all
docker compose --env-file secrets.env logs --tail=100 db-migrator server
curl --fail https://api.dicto.example.com/
The migration container should show a successful exit; the other containers should be running. Route your management hostname to
management:80, sign in, and follow client setup for a test recording and transcription.
Optional: add Prometheus#
Download prometheus.compose.yaml and prometheus.yaml into the same directory.
In server.yaml, replace the monitoring section with:
monitoring:
enabled: true
host: 0.0.0.0
port: 9091
The monitoring endpoint stays on the internal container network. The optional Compose file adds Prometheus:
services:
prometheus:
image: prom/prometheus:latest
restart: unless-stopped
volumes:
- ./prometheus.yaml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
depends_on:
server:
condition: service_started
volumes:
prometheus-data:
Its scrape configuration targets Dicto's monitoring port:
global:
scrape_interval: 15s
scrape_configs:
- job_name: dicto
metrics_path: /metrics
static_configs:
- targets: ["server:9091"]
Start the additional service and reload the server configuration:
docker compose --env-file secrets.env -f compose.yaml -f prometheus.compose.yaml up -d --force-recreate server prometheus
Route an administrator-only hostname through your reverse proxy to prometheus:9090 and open the monitoring interface there. Check the
dicto target is UP. The Prometheus configuration reference
describes scrape settings.
To make Prometheus available as a source for management dashboards, add this server configuration:
management:
sources:
monitoring:
type: prometheus
url: http://prometheus:9090
The management UI queries this source through the Dicto server. Add dashboard queries using the monitoring
source when you want to display these metrics; the built-in database dashboards remain available.
Updates and backups#
Back up PostgreSQL and recording storage together and test restoration. To update the :latest
images, pull them and rerun the migration container before replacing the server:
docker compose --env-file secrets.env stop server management
docker compose --env-file secrets.env pull server db-migrator management
docker compose --env-file secrets.env run --rm db-migrator migrate
docker compose --env-file secrets.env up -d server management
Use both Compose files when managing an installation with Prometheus. An older server image does not reverse database migrations.
