Sign inSign up

croit/ccd

By croit

Updated about 2 months ago

The croit Central Dashboard gives you a centralized view of all your Ceph installations worldwide.

Image
Monitoring & observability
0

151

croit/ccd repository overview

Croit Central Dashboard

One screen for all your storage clusters.

If you run more than one Ceph or croit cluster, you log into each one separately to answer simple questions: is everything healthy, how full are we, what changed last night, which disk is about to fail. The Croit Central Dashboard (CCD) answers those questions once, for the whole fleet, and lets you jump into a single cluster when you want the detail.

CCD runs as one container next to your clusters. It reads them through their official API and never changes anything on a cluster. Adding CCD cannot break a running cluster, and it needs no agent installed anywhere.

docker run -d --name ccd -p 8234:8234 -v ccd-data:/data \
  -e CCD_PUBLIC_URL=http://localhost:8234 \
  -e CCD_SECRET_KEY="$(openssl rand -hex 32)" \
  -e OIDC_DISCOVERY_URL=https://id.example.com/application/o/ccd/.well-known/openid-configuration \
  -e OIDC_CLIENT_ID=ccd \
  -e OIDC_CLIENT_SECRET=... \
  croit/ccd:latest

CCD signs in through your own identity provider, so those four OIDC_* values are required. Setting up single sign-on walks through it with authentik as the worked example.


What it gives you

The fleet at a glance. Total capacity and how much is used, read and write throughput, IOPS, servers and OSDs online, and how many clusters are healthy, degraded or unreachable. One tile per cluster, worst first, so a problem finds you instead of the other way round.

Aggregated numbers you can trust. A cluster that is unreachable never quietly contributes a zero. Every fleet total says how many clusters it covers, so a suspiciously low number explains itself.

Capacity planning that is worth acting on. A fill history per cluster and a forecast of how many days remain until the near-full and full thresholds, so hardware gets ordered before it is urgent. Ceph fills to its fullest OSD, not to the average, and CCD tracks the fullest OSD for exactly that reason.

Findings from every cluster in one list, most severe first: health warnings, recommendations, a disk whose SMART report went bad, an OSD that is down, a version that has fallen behind the rest of the fleet. Mute one you have consciously accepted and it stops nagging.

Fleet-wide lists you can search and sort across all clusters at once: servers, services and gateways, OSDs, pools, physical disks with their SMART health, and recent tasks. No more opening five tabs to find which host has the failing disk.

Logs from every cluster in one viewer. Follow them live, or walk an absolute time range. Pick a single OSD or host from a table and the viewer narrows to it.

Charts for latency, IOPS, throughput, recovery, placement-group and OSD states, per cluster or summed across the fleet, plus per-OSD latency inside one cluster, which is how you find the one slow disk dragging a pool down.

A world map with a marker per cluster, coloured by health. Useful when clusters sit in different data centres, countries or customers.

One audit trail covering what happened in CCD and on the clusters, so "who changed that" has a single answer.

Single sign-on, and read-only by default. CCD has no user accounts of its own. Your existing logins and groups keep working, and anyone who can log in gets read-only access unless you grant them more.


Which clusters can I connect?

croit clusters are fully supported and get every feature above.

Plain Ceph clusters connect through the Ceph Manager dashboard API. You get health, capacity, throughput, servers, services, OSDs, pools, the cluster log, the audit trail and Ceph's own health checks. Some things depend on the cluster: disk inventory and SMART need the cephadm orchestrator, and there are no charts, because the Ceph API serves no time series. The capacity trend still works, because CCD records that itself.

CCD tells you in plain words when a feature is unavailable and why, instead of showing an empty page. It also detects the cluster type from the URL when you add it, so you do not have to know.


Which tag should I use?

TagUse it when
croit/ccd:latestYou just want the current version. Points at the newest release. This is the right choice for almost everyone.
croit/ccd:v2608You want to stay on one release line and pick up its fixes, without moving to a newer line on your own schedule.
croit/ccd:v2608.1.0You need an exact, unchanging version, for example in a change-controlled environment.
croit/ccd:nightlyYou want to try something that is not released yet. Not for production.
Reading a version number
2608.1.0
│    │ └── patch level of that release
│    └──── the first release of that month
└───────── year 26, month 08

So 2608.1.0 is the first release from August 2026, and 2609.2.0 is the second one from September 2026. You can tell at a glance how current your installation is. A number ending in .0 is always a release; :nightly builds carry a higher last digit and are never published as a version tag.

The version your installation runs is shown at the bottom of every page, so a support request can always name it.


Getting started

What you need
  • A host with Docker, reachable by your users, on linux/amd64. That is the only architecture CCD is published for, which covers ordinary servers. On an ARM machine, including an Apple Silicon Mac, see the note below.
  • Network access from that host to each cluster's API. Self-signed certificates are fine and never need an exception.
  • An OpenID Connect identity provider: authentik, Keycloak, Entra ID, Okta, Google Workspace, whatever you already run.
  • Per cluster, a read-only credential, created in that cluster's own user management:
    • croit: an API token, or a user with read-only permissions.
    • Ceph: a read-only dashboard user, because the Ceph API issues no long-lived tokens:
      ceph mgr module enable dashboard
      ceph dashboard ac-user-create ccd -i password-file read-only
      
With Docker Compose

Save this as docker-compose.yml, put your values in ccd.env next to it, and run docker compose up -d:

services:
  ccd:
    image: croit/ccd:latest
    env_file: ccd.env
    ports:
      - "8234:8234"
    volumes:
      - ccd-data:/data
    restart: unless-stopped

volumes:
  ccd-data:
# ccd.env
CCD_PUBLIC_URL=https://ccd.example.com
CCD_SECRET_KEY=<output of: openssl rand -hex 32>

OIDC_DISCOVERY_URL=https://id.example.com/application/o/ccd/.well-known/openid-configuration
OIDC_CLIENT_ID=ccd
OIDC_CLIENT_SECRET=<from your identity provider>
OIDC_ROLE_MAPPING={"storage-admins":"admin","noc":"viewer"}

ccd.env holds secrets. Keep it readable only by the user running Docker.

Running on an ARM machine

CCD is published for linux/amd64 only. On an ARM host, an Apple Silicon Mac being the usual case, docker pull refuses with:

no matching manifest for linux/arm64/v8 in the manifest list entries

Ask for the amd64 image explicitly and Docker runs it under emulation:

docker run -d --name ccd --platform linux/amd64 -p 8234:8234 ...

In compose, add one line to the service:

services:
  ccd:
    image: croit/ccd:latest
    platform: linux/amd64

Emulation is noticeably slower, which is fine for a look around and not something to run a production installation on. Put CCD on an ordinary amd64 server for that.

First login

Open your CCD_PUBLIC_URL and choose Sign in with SSO. You are sent to your identity provider and back; there is no separate CCD password.

Two roles:

  • admin adds, edits and removes clusters, mutes findings and sees the audit trail.
  • viewer sees everything else, read-only.

Everyone who can log in is a viewer unless OIDC_ROLE_MAPPING grants more, so a new installation cannot be changed by accident.

Look around before connecting anything

A new installation is not empty. It comes with four demo clusters in different locations, with moving charts, live logs, health findings, tasks and audit entries. They are built into CCD, so nothing is contacted over the network and there is nothing to set up. It is the fastest way to see whether CCD is for you.

The demo clusters have no cluster interface behind them, so there is no link to open one. Everything else behaves like the real thing.

Delete them in the cluster list when you no longer want them and they stay gone. To skip them from the start, set CCD_DEMO_CLUSTER=off before the first launch.

Add your clusters

Go to Clusters, then Add cluster:

FieldWhat to put in
NameHow it appears in CCD, for example "Frankfurt production"
URLThe cluster's API address, for example https://10.0.0.10
Typecroit or Ceph Manager. Press Detect from URL and CCD works it out.
CredentialThe read-only token or user from above
LocationOptional city, latitude and longitude, for the world map

The tile turns green within a few seconds and the fleet numbers include it. Nothing else needs configuring: CCD polls each cluster continuously and pushes updates to open browser tabs, so pages refresh themselves.


Setting up single sign-on

CCD needs four values from your identity provider, and one redirect URI registered on their side:

<CCD_PUBLIC_URL>/auth/oidc/callback

It has to match exactly, including http versus https and the port. For a first local test that is http://localhost:8234/auth/oidc/callback.

Worked example: authentik

1. Create the provider. Applications, Providers, Create, OAuth2/OpenID Provider.

FieldValue
NameCCD
Authorization flowyour usual one, for example default-provider-authorization-explicit-consent
Client typeConfidential
Client ID / Client Secretauthentik generates both; copy them
Redirect URIshttps://ccd.example.com/auth/oidc/callback, match set to Strict
Signing Keypick your certificate, so the ID token is signed with RS256
Scopesleave authentik's defaults for openid, email and profile

2. Create the application. Applications, Applications, Create. Name it Croit Central Dashboard, set the slug to ccd, and select the provider from step 1. The slug is what appears in the discovery URL.

3. Fill in CCD. The discovery URL follows the application slug:

OIDC_DISCOVERY_URL=https://authentik.example.com/application/o/ccd/.well-known/openid-configuration
OIDC_CLIENT_ID=<Client ID from step 1>
OIDC_CLIENT_SECRET=<Client Secret from step 1>

4. Map your groups to roles. Create a group in authentik, for example storage-admins, and map it:

OIDC_ROLE_MAPPING={"storage-admins":"admin","noc":"viewer"}

CCD reads the group names out of the groups claim. Recent authentik versions include that claim in the default profile scope, so this usually works with no further setup.

If your login lands on viewer although your group matches, the claim is not being sent. Add a scope mapping in Customisation, Property Mappings, Create, Scope Mapping:

FieldValue
NameCCD groups
Scope namegroups
Expressionreturn [group.name for group in user.ak_groups.all()]

Add that mapping to the provider's selected scopes, and request it in CCD:

OIDC_SCOPES=openid email profile groups

This step depends on your authentik version, which is why it is written as a fallback rather than a required step.

To see what CCD actually received, log in and read the container log. It reports every sign-in, and the three cases look different on purpose:

[ccd][oidc] martin: claim "groups" [storage-admins] -> roles [admin]
[ccd][oidc] jo: claim "groups" absent -> roles [viewer] (no group matched, used OIDC_DEFAULT_ROLES)
[ccd][oidc] kim: claim "groups" [devs] -> roles [viewer] (no group matched, used OIDC_DEFAULT_ROLES)

absent means your provider is not sending the claim, so fix it there. A claim that arrives but matches nothing means OIDC_ROLE_MAPPING needs that group name.

Other providers

The same three things apply everywhere: a confidential client, the redirect URI above, and a claim carrying group names. If your provider calls that claim something else, point CCD at it with OIDC_GROUPS_CLAIM, for example roles or wids.


Configuration reference

Everything is set through environment variables. CCD reads them once at start, so restart the container after a change.

Basics
SettingRequiredMeaning
CCD_PUBLIC_URLyesThe address your users type into the browser, for example https://ccd.example.com. It builds the single sign-on redirect URI, so it must match what your provider allows.
CCD_SECRET_KEYyes32 random bytes that encrypt stored cluster credentials and sign login sessions. Generate with openssl rand -hex 32. Back it up: lose it and every cluster credential has to be entered again. Changing it logs everyone out.
CCD_PORTnoPort inside the container, default 8234. Change the port mapping too if you change this.
CCD_DBnoDefault sqlite:///data/ccd.db, which is the mounted volume and right for almost everyone. For an external database: postgres://user:password@host:5432/database.
CCD_DEMO_CLUSTERnoon by default: registers the four demo clusters while no real cluster is configured. Set to off to skip them. Deleting them in the interface is permanent either way.
Single sign-on
SettingRequiredMeaning
OIDC_DISCOVERY_URLyesYour provider's discovery document, usually ending in /.well-known/openid-configuration.
OIDC_CLIENT_IDyesThe client id issued for CCD.
OIDC_CLIENT_SECRETyesThe matching client secret.
OIDC_ROLE_MAPPINGnoMaps a group to a CCD role, as JSON: {"storage-admins":"admin","noc":"viewer"}. Roles are admin and viewer.
OIDC_DEFAULT_ROLESnoRoles for a user whose groups match nothing. Default ["viewer"].
OIDC_GROUPS_CLAIMnoWhich claim carries the group names, default groups.
OIDC_SCOPESnoRequested scopes, default openid email profile.
OIDC_CLIENT_AUTHnobasic (default) or post. Change only if your provider requires it.
Everything together

A complete docker-compose.yml with every setting in place. Copy it, replace the five values marked CHANGE ME, and delete the optional lines you do not need:

services:
  ccd:
    image: croit/ccd:latest
    restart: unless-stopped
    ports:
      # host:container. Change the host side if 8234 is taken; put CCD behind
      # your own reverse proxy for TLS.
      - "8234:8234"
    volumes:
      # Cluster list, capacity history, audit trail and settings. Back this up.
      - ccd-data:/data
    environment:
      # ---- required ----------------------------------------------------------
      # The address your users open. Must match the redirect URI you registered.
      CCD_PUBLIC_URL: https://ccd.example.com          # CHANGE ME
      # openssl rand -hex 32 -- back this up, see the note below.
      CCD_SECRET_KEY: 0000000000000000000000000000000000000000000000000000000000000000  # CHANGE ME

      OIDC_DISCOVERY_URL: https://id.example.com/application/o/ccd/.well-known/openid-configuration  # CHANGE ME
      OIDC_CLIENT_ID: ccd                              # CHANGE ME
      OIDC_CLIENT_SECRET: change-me                    # CHANGE ME

      # ---- optional, shown with their defaults -------------------------------
      # Which of your groups may change things. Everyone else is read-only.
      OIDC_ROLE_MAPPING: '{"storage-admins":"admin","noc":"viewer"}'
      OIDC_DEFAULT_ROLES: '["viewer"]'
      OIDC_GROUPS_CLAIM: groups
      OIDC_SCOPES: openid email profile
      OIDC_CLIENT_AUTH: basic

      CCD_PORT: 8234
      CCD_DB: sqlite:///data/ccd.db
      # Set to off to start without the four built-in demo clusters.
      CCD_DEMO_CLUSTER: "on"

volumes:
  ccd-data:

Note the quoting: OIDC_ROLE_MAPPING, OIDC_DEFAULT_ROLES and CCD_DEMO_CLUSTER need single or double quotes in YAML, because {, [ and a bare on mean something else there.

Prefer to keep secrets out of the compose file? Replace the whole environment: block with env_file: ccd.env and put the same KEY=value lines in that file, without quotes.


Running it

Update:

docker compose pull && docker compose up -d

Back up the ccd-data volume, mounted at /data. It holds the cluster list, capacity history, audit trail and settings, so CCD can be recreated from it at any time. Cluster credentials inside it are encrypted with your CCD_SECRET_KEY, which therefore belongs in your backup too, stored separately.

Logs: docker logs ccd, or docker compose logs -f ccd.


If something does not work

The container stops right after starting. CCD refuses to run with an incomplete configuration and names each missing value in its log. Read docker logs ccd. The usual causes are a missing CCD_SECRET_KEY, a key shorter than 32 characters, or a mistyped OIDC_* value.

Sign-in fails at the identity provider. The redirect URI does not match. It has to be exactly <CCD_PUBLIC_URL>/auth/oidc/callback, including scheme and port.

You can log in but cannot add clusters. Your groups mapped to viewer. Add your group to OIDC_ROLE_MAPPING as admin and restart. If it still happens, the group claim is not arriving; see the authentik fallback above.

A cluster stays unreachable. The cluster tile shows the reason. Check that the host can reach the URL, that the scheme and port are right, and that the token or user still exists on the cluster. Self-signed certificates are accepted and are never the cause.

A number looks too low. An unreachable cluster contributes nothing rather than a zero, and CCD marks how many clusters each total covers. Look for a cluster that is not currently reachable.

A feature is missing for one cluster. CCD shows what is unavailable and why, usually a Ceph cluster without the cephadm orchestrator, or charts, which plain Ceph cannot serve.


Licence and support

Copyright (c) 2026 croit GmbH. Use of this image is governed by the licence inside it at /app/LICENSE.md; the third-party components it contains are listed in /app/NOTICE.md.

Questions, or want CCD to do something it does not do yet? https://croit.io

Tag summary

Content type

Image

Digest

sha256:ecc3d9a1c

Size

83.5 MB

Last updated

about 2 months ago

docker pull croit/ccd:nightly