Hello World module reference

The cloudsmith-module-hello repository is the canonical reference implementation for CloudSmith modules. It exercises every contract in the ICloudSmithModule lifecycle and every platform integration point (nav slots, health checks, background jobs, API endpoints) in a minimal, well-commented codebase.

Use it as a fork template when building your own module.

Source: github.com/cloudsmith-cloud/cloudsmith-module-hello


What the Hello World module does

Capability Detail
Nav slot Registers “Hello World” in the primary sidebar (nav.primary)
API endpoint GET /api/hello — returns a JSON greeting with module ID, version, and timestamp
Module page GET /modules/hello — HTML page rendered in the portal nav slot target
Health check hello-module — always Healthy; surfaced on the Platform Health page
Background job Heartbeat timer — logs a message every 60 seconds

Repository structure

cloudsmith-module-hello/
├── src/
│   └── CloudSmith.Module.Hello/
│       ├── HelloModule.cs          — ICloudSmithModule implementation (all 5 lifecycle hooks)
│       ├── HelloHealthCheck.cs     — IHealthCheck implementation
│       ├── HelloEndpoints.cs       — Minimal API endpoint registration
│       ├── Program.cs              — Host builder and SDK wiring
│       └── appsettings.json        — Default configuration
├── module.json                     — Module manifest (schema-validated at install time)
├── Dockerfile                      — Multi-stage build: SDK restore → publish → runtime image
└── .github/workflows/ci.yml        — CI: build + ghcr.io push on every main commit

ICloudSmithModule lifecycle

Every module must implement ICloudSmithModule. The Module Lifecycle Manager (MLM) calls the five lifecycle methods in the following order:

ConfigureServices(IServiceCollection)
    → ConfigureAsync(IModuleContext, CancellationToken)
        → StartAsync(CancellationToken)
            → [module serves traffic]
        → StopAsync(CancellationToken)
    → DisposeAsync()
Method When called What to do
ConfigureServices Host builder construction — DI container is not yet built Register DI services: repositories, HTTP clients, domain services
ConfigureAsync After DI container is built, before any StartAsync fires Obtain a logger from IModuleContext, subscribe to the event bus, run cache warm-up
StartAsync After the host is fully started and ready for traffic Start background timers and workers
StopAsync On SIGTERM or host stop — 30-second grace period Signal background workers to stop; flush write buffers
DisposeAsync After StopAsync completes or times out Dispose IDisposable fields and event bus subscription handles

The MLM guarantees that all ConfigureAsync calls across all loaded modules complete before any StartAsync fires. A failure in ConfigureAsync transitions the module to Failed state and logs error CS-MOD-5004. The host continues loading other modules regardless.

IModuleContext

ConfigureAsync receives an IModuleContext that provides:

  • GetLogger<T>() — Serilog-backed logger pre-enriched with module_id and module_version
  • OrgId — organization scope for multi-tenant deployments
  • EventBus — subscribe to and publish platform domain events

The [CloudSmithSlotContribution] attribute

Nav slot registrations are declared via [CloudSmithSlotContribution] on the module class. The MLM reads these attributes at load time and wires the nav entries into the portal before StartAsync fires.

[CloudSmithSlotContribution(CloudSmithSlots.NavPrimary, Order = 100)]
[CloudSmithSlotContribution(CloudSmithSlots.ModulePage)]
public sealed class HelloModule : ICloudSmithModule
Slot Constant What it does
nav.primary CloudSmithSlots.NavPrimary Adds the module’s nav entry to the primary sidebar. The label, icon, and route come from module.json under spec.navSlots.
module.page CloudSmithSlots.ModulePage Marks the module as contributing a full portal page, rendered in the nav slot target route.

The Order parameter on NavPrimary controls the position of the nav entry relative to other modules. Lower numbers appear higher in the list. The Hello module uses order 100, which places it below all built-in platform nav items (order 0–50).

Slot registrations in the attribute must match the corresponding entries in module.json under spec.navSlots. A mismatch causes a warning at load time but does not prevent the module from starting.


module.json

module.json is the module manifest. It is validated against the CloudSmith module manifest JSON schema at install time. A manifest that fails validation causes install to abort with a descriptive error.

{
  "$schema": "https://cloudsmith.cloud/schemas/module-manifest/v1.json",
  "metadata": {
    "id": "cloudsmith-module-hello",
    "name": "Hello World",
    "version": "0.1.0",
    "description": "Reference module demonstrating CloudSmith module install, uninstall, and nav slot registration.",
    "author": "CloudSmith",
    "publisher": "cloudsmith-cloud",
    "license": "Apache-2.0",
    "repositoryUrl": "https://github.com/cloudsmith-cloud/cloudsmith-module-hello"
  },
  "spec": {
    "sdkVersion": ">=0.1.0 <1.0.0",
    "image": "ghcr.io/cloudsmith-cloud/cloudsmith-module-hello:latest",
    "apiBasePath": "/api/hello",
    "healthEndpoint": "/health",
    "permissionsRequired": [],
    "navSlots": [
      {
        "slotId": "nav.primary",
        "id": "hello-world",
        "label": "Hello World",
        "icon": "Handshake",
        "route": "/modules/hello",
        "order": 100
      }
    ],
    "healthChecks": [
      {
        "name": "hello-module",
        "path": "/health",
        "tags": ["informational"]
      }
    ],
    "backgroundJobs": [
      {
        "id": "hello-heartbeat",
        "description": "Logs a heartbeat message every 60 seconds.",
        "intervalSeconds": 60
      }
    ]
  }
}

Required manifest fields

Field Type Description
metadata.id string Stable kebab-case identifier — must match ModuleInfo.Id in code. A mismatch causes CS-MOD-5001 and aborts load.
metadata.name string Human-readable display name shown in the catalog
metadata.version string SemVer 2.0 version
metadata.description string One-sentence description shown in the catalog
metadata.author string Author name or organization
spec.sdkVersion string SemVer range for the required CloudSmith.Sdk version
spec.image string Fully-qualified OCI image reference
spec.apiBasePath string URL prefix for this module’s API routes
spec.healthEndpoint string Path the MLM calls to verify the module is running

Optional fields: spec.navSlots, spec.permissionsRequired, spec.healthChecks, spec.backgroundJobs.

The icon value in spec.navSlots must be a valid Lucide icon name. See lucide.dev/icons.


Health checks

HelloHealthCheck implements IHealthCheck. The MLM registers it with the platform health aggregator and surfaces it on:

  • GET /health/ready — aggregated platform health endpoint
  • Platform Management → Platform Health in the portal, under the name hello-module

Health check tags control how the platform treats a failure:

Tag Effect
informational Failure is reported but does not change overall platform health status
degradable Failure sets overall platform health to Degraded (HTTP 200)
critical Failure sets overall platform health to Unhealthy (HTTP 503)

The Hello module uses informational because it is a reference module with no effect on platform availability. Production modules should use degradable or critical based on their impact.


CI workflow

The CI workflow in .github/workflows/ci.yml runs on every push to main and on all pull requests.

Trigger Action
Push to main Build image, push to ghcr.io with :latest and :<git-sha> tags
Pull request Build only — validates the Dockerfile and module manifest; no image push

The workflow attaches the following OCI labels required for catalog discovery:

org.cloudsmith.module=true
org.cloudsmith.module.id=cloudsmith-module-hello
org.cloudsmith.module.version=0.1.0
org.opencontainers.image.source=https://github.com/cloudsmith-cloud/cloudsmith-module-hello

The platform’s GHCR-based module catalog filters packages by the org.cloudsmith.module=true label. Any image published to a GHCR repository under ghcr.io/<your-org>/ that carries this label will appear in the catalog automatically.


Fork and build your own module

Step 1 — Fork the repository

gh repo fork cloudsmith-cloud/cloudsmith-module-hello \
  --clone \
  --org <your-github-org>

Rename the fork to <your-org>-module-<name>.

Step 2 — Rename the module

Replace every occurrence of Hello / hello with your module name across these files:

File What to change
src/CloudSmith.Module.Hello/ (directory) Rename to src/CloudSmith.Module.<YourName>/
CloudSmith.Module.Hello.csproj Rename to CloudSmith.Module.<YourName>.csproj
HelloModule.cs Rename file and class; update ModuleInfo.Id and ModuleInfo.Version
HelloHealthCheck.cs Rename file and class; update Name constant
HelloEndpoints.cs Rename file and class; update route prefixes
Program.cs Update MapCloudSmithModule<HelloModule>() and MapHelloEndpoints() references

Step 3 — Update module.json

Set your module’s identity fields and image reference:

{
  "metadata": {
    "id": "<your-org>-module-<name>",
    "name": "<Your Module Display Name>",
    "version": "0.1.0",
    "description": "<One-sentence description>",
    "author": "<Your Name or Org>",
    "publisher": "<your-github-org>"
  },
  "spec": {
    "image": "ghcr.io/<your-github-org>/<your-org>-module-<name>:latest",
    "apiBasePath": "/api/<name>",
    "navSlots": [
      {
        "slotId": "nav.primary",
        "id": "<name>",
        "label": "<Your Label>",
        "icon": "<LucideIconName>",
        "route": "/modules/<name>",
        "order": 200
      }
    ]
  }
}

Step 4 — Implement your module logic

File Purpose
HelloModule.cs Implement the 5 lifecycle hooks; register event bus subscriptions in ConfigureAsync and dispose them in DisposeAsync
HelloEndpoints.cs Add Minimal API routes; protect them with .RequireAuthorization()
HelloHealthCheck.cs Check backing service availability (database, external APIs) and return Unhealthy when unavailable

The inline comments in each file explain the contract touchpoints.

Step 5 — Push and install

Push to main in your fork. The CI workflow publishes the image to ghcr.io/<your-org>/<repo>:latest.

To install the module in a running CloudSmith instance:

POST /api/modules/install
Content-Type: application/json

{
  "imageRef": "ghcr.io/<your-org>/<repo>:latest",
  "manifestUrl": "https://raw.githubusercontent.com/<your-org>/<repo>/main/module.json"
}

After install, the nav entry you configured appears in the portal sidebar within approximately 5 seconds.

To uninstall:

DELETE /api/modules/<module-id>