-
Notifications
You must be signed in to change notification settings - Fork 0
Reference Technical Specification
- API Overview
- Endpoint Specification
- Data Models
- Commands & Queries
- Services & Interfaces
- Wizard API & State Machine
- Deployment Engine – Process
- Manifest Schema (formal)
- Docker Integration
- UI-API Contract
ReadyStackGo provides a clearly defined HTTP API.
All endpoints are accessible under /api/v1/.
- During Wizard: no auth
- After that:
- Local Login (JWT or Cookie)
- Optional OIDC
- Roles:
admin,operator
{
"success": true,
"message": "optional",
"data": {}
}{
"success": false,
"message": "Error description",
"errorCode": "XYZ_ERROR"
}This chapter describes all API endpoints in detail. Each endpoint contains:
- Path
- Method
- Role authorization
- Request Body
- Response Body
- Error codes
Lists all containers on the host.
Roles: admin, operator Auth: required Response:
{
"success": true,
"data": [
{
"id": "string",
"name": "string",
"image": "string",
"state": "running|exited|paused",
"created": "2025-03-10T12:00:00Z",
"ports": [
{ "private": 8080, "public": 8443, "protocol": "tcp" }
]
}
]
}Starts a container.
Body:
{ "id": "string" }Roles: admin, operator
Stops a container.
Body:
{ "id": "string" }Roles: admin, operator
Returns the current status of the Setup Wizard.
{
"success": true,
"data": {
"state": "NotStarted|AdminCreated|OrganizationSet|ConnectionsSet|Installed"
}
}Creates the first admin user.
Body:
{
"username": "string",
"password": "string"
}Response:
{ "success": true }Creates the organization.
Body:
{
"id": "string",
"name": "string"
}Sets global connections.
{
"transport": "string",
"persistence": "string",
"eventStore": "string?"
}Installs the stack based on a manifest.
Response:
{
"success": true,
"data": {
"installedVersion": "4.3.0"
}
}Lists all available manifests.
Returns the installed state.
Installs the specified manifest.
Error codes:
MANIFEST_NOT_FOUNDDEPLOYMENT_FAILEDINCOMPATIBLE_VERSION
Shows TLS status.
Upload of a custom certificate (multipart).
Simple/Advanced Mode.
This chapter contains all data models that ReadyStackGo needs. They are divided into three categories:
- Domain Models – internal business objects
- DTOs – API input and output
- Config Models – objects representing JSON configuration files
Represents a Docker container on the host.
public sealed class ContainerInfo
{
public string Id { get; init; }
public string Name { get; init; }
public string Image { get; init; }
public string State { get; init; }
public DateTime Created { get; init; }
public List<PortMapping> Ports { get; init; }
}
public sealed class PortMapping
{
public int Private { get; init; }
public int? Public { get; init; }
public string Protocol { get; init; }
}Represents the currently installed version.
public sealed class ReleaseStatus
{
public string InstalledStackVersion { get; init; }
public Dictionary<string, string> InstalledContexts { get; init; }
public DateTime InstallDate { get; init; }
}Describes what steps are necessary to install a manifest.
public sealed class DeploymentPlan
{
public List<DeploymentAction> Actions { get; init; }
}
public sealed class DeploymentAction
{
public string Type { get; init; } // stop | remove | create | start
public string ContextName { get; init; }
}public sealed class ContainerDto
{
public string Id { get; init; }
public string Name { get; init; }
public string Image { get; init; }
public string State { get; init; }
public DateTime Created { get; init; }
public IEnumerable<PortMappingDto> Ports { get; init; }
}
public sealed class PortMappingDto
{
public int Private { get; init; }
public int? Public { get; init; }
public string Protocol { get; init; }
}public sealed class WizardStatusDto
{
public string State { get; init; }
}public sealed class InstallResultDto
{
public string InstalledVersion { get; init; }
}public sealed class SystemSettings
{
public OrganizationInfo Organization { get; init; }
public string BaseUrl { get; init; }
public int HttpPort { get; init; }
public int HttpsPort { get; init; }
public string DockerNetwork { get; init; }
public string Mode { get; init; }
public string WizardState { get; init; }
}public sealed class SecuritySettings
{
public string AuthMode { get; init; }
public LocalAdminSettings LocalAdmin { get; init; }
public OidcSettings ExternalProvider { get; init; }
public bool LocalAdminFallbackEnabled { get; init; }
}public sealed class TlsSettings
{
public string TlsMode { get; init; }
public string CertificatePath { get; init; }
public string CertificatePassword { get; init; }
public int HttpsPort { get; init; }
public bool HttpEnabled { get; init; }
public string TerminatingContext { get; init; }
}public sealed class ContextSettings
{
public string Mode { get; init; } // Simple | Advanced
public Dictionary<string, string> GlobalConnections { get; init; }
public Dictionary<string, ContextConnectionOverride> Contexts { get; init; }
}
public sealed class ContextConnectionOverride
{
public Dictionary<string, string> Connections { get; init; }
}public sealed class FeatureSettings
{
public Dictionary<string, bool> Features { get; init; }
}public sealed class ReleaseFile
{
public string InstalledStackVersion { get; init; }
public Dictionary<string, string> InstalledContexts { get; init; }
public DateTime InstallDate { get; init; }
}ReadyStackGo uses a Dispatcher Pattern instead of MediatR. All actions run through:
- Commands (write/state-changing)
- Queries (read)
Each Command/Query is executed via the IDispatcher.
Starts a Docker container.
public sealed record StartContainerCommand(string Id) : ICommand<bool>;public sealed class StartContainerHandler
: ICommandHandler<StartContainerCommand, bool>
{
private readonly IDockerService _docker;
public StartContainerHandler(IDockerService docker)
=> _docker = docker;
public Task<bool> HandleAsync(StartContainerCommand cmd, CancellationToken ct)
=> _docker.StartAsync(cmd.Id);
}public sealed record StopContainerCommand(string Id) : ICommand<bool>;Installs a manifest.
public sealed record InstallStackCommand(string StackVersion)
: ICommand<InstallResultDto>;Handler executes:
- Load manifest
- Check version
- Generate DeploymentPlan
- Execute deployment
- Update rsgo.release.json
public sealed record ListContainersQuery(bool IncludeStopped)
: IQuery<List<ContainerInfo>>;public sealed record GetReleaseStatusQuery()
: IQuery<ReleaseStatus>;public interface IDispatcher
{
Task<TResult> SendAsync<TResult>(ICommand<TResult> command, CancellationToken ct = default);
Task<TResult> QueryAsync<TResult>(IQuery<TResult> query, CancellationToken ct = default);
}public sealed class Dispatcher : IDispatcher
{
private readonly IServiceProvider _sp;
public Dispatcher(IServiceProvider sp)
=> _sp = sp;
public Task<TResult> SendAsync<TResult>(ICommand<TResult> cmd, CancellationToken ct)
{
var handlerType = typeof(ICommandHandler<,>).MakeGenericType(cmd.GetType(), typeof(TResult));
dynamic handler = _sp.GetRequiredService(handlerType);
return handler.HandleAsync((dynamic)cmd, ct);
}
public Task<TResult> QueryAsync<TResult>(IQuery<TResult> query, CancellationToken ct)
{
var handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = _sp.GetRequiredService(handlerType);
return handler.HandleAsync((dynamic)query, ct);
}
}- no reflection magic like MediatR
- full compilability of all handlers
- transparent resolution via DI
- own policies / pipelines easily integratable
- 100% compatible with FastEndpoints
This chapter describes the most important internal services of ReadyStackGo. Each service follows the interface-first principle and has a clearly defined responsibility.
Abstracts the Docker API.
public interface IDockerService
{
Task<IReadOnlyList<ContainerInfo>> ListAsync(bool includeStopped);
Task<bool> StartAsync(string id);
Task<bool> StopAsync(string id);
Task<bool> RemoveAsync(string name);
Task<bool> CreateAndStartAsync(ContainerCreateModel model);
Task<bool> NetworkEnsureExistsAsync(string name);
}public sealed class ContainerCreateModel
{
public string Name { get; init; }
public string Image { get; init; }
public Dictionary<string, string> Env { get; init; }
public IEnumerable<int> ExposedPorts { get; init; }
public string Network { get; init; }
public string RestartPolicy { get; init; } = "unless-stopped";
}Manages all config files (rsgo-config Volume).
public interface IConfigStore
{
Task<SystemSettings> LoadSystemAsync();
Task SaveSystemAsync(SystemSettings settings);
Task<SecuritySettings> LoadSecurityAsync();
Task SaveSecurityAsync(SecuritySettings settings);
Task<TlsSettings> LoadTlsAsync();
Task SaveTlsAsync(TlsSettings settings);
Task<ContextSettings> LoadContextsAsync();
Task SaveContextsAsync(ContextSettings settings);
Task<FeatureSettings> LoadFeaturesAsync();
Task SaveFeaturesAsync(FeatureSettings settings);
Task<ReleaseFile> LoadReleaseAsync();
Task SaveReleaseAsync(ReleaseFile file);
}Implementation: Files are always completely replaced (Write-All), never patched.
Creates certificates, validates certificates, and loads custom certificates.
public interface ITlsService
{
Task<TlsGenerateResult> GenerateSelfSignedAsync(string commonName);
Task<bool> ValidateCustomCertificateAsync(string path, string password);
Task<bool> InstallCustomCertificateAsync(string path, string password);
}public sealed class TlsGenerateResult
{
public string Path { get; init; }
public string Password { get; init; }
}Loads release manifests and validates their schema.
public interface IManifestProvider
{
Task<IReadOnlyList<ReleaseManifest>> LoadAllAsync();
Task<ReleaseManifest> LoadVersionAsync(string version);
Task<bool> ExistsAsync(string version);
}Executes a manifest completely.
public interface IDeploymentEngine
{
Task<DeploymentResult> InstallAsync(ReleaseManifest manifest);
}public sealed class DeploymentResult
{
public bool Success { get; init; }
public string Error { get; init; }
public ReleaseFile UpdatedRelease { get; init; }
}Generates environment variables for each context.
public interface IEnvVarService
{
Task<Dictionary<string, string>> GenerateForContextAsync(
string contextName,
ReleaseManifest manifest
);
}The result consists of:
- system variables
- feature flags
- context connections
- manifest env overrides
Will be used for CI/CD triggers and external events.
ReadyStackGo consistently uses DI and never a global Service Locator.
The ReadyStackGo Wizard is based entirely on a clearly defined State Machine. The API controls exclusively transitions of this state machine.
The Wizard knows the following states:
| State | Description |
|---|---|
NotStarted |
rsgo.config does not exist or is empty |
AdminCreated |
The administrator was created |
OrganizationSet |
Organization was defined |
ConnectionsSet |
Connections were saved |
Installed |
Stack is installed, Wizard deactivated |
All states are stored in rsgo.system.json:
{
"wizardState": "OrganizationSet"
}- Wizard is active when
wizardState != Installed
NotStarted → AdminCreated → OrganizationSet → ConnectionsSet → Installed
- e.g.,
ConnectionsSet → AdminCreatedis forbidden
Returns the current state.
Creates the first admin.
Validations:
- Username must not be empty
- Password must meet minimum length
Result:
- wizardState =
AdminCreated
Saves:
- Organization ID
- Organization Name
Result:
- wizardState =
OrganizationSet
Saves the basic connections:
- Transport
- Persistence
- EventStore (optional)
Result:
- wizardState =
ConnectionsSet
Installs the complete stack.
Process:
- Select manifest
- Generate deployment plan
- Execute Deployment Engine
- Save release file
- wizardState =
Installed
| Code | Meaning |
|---|---|
WIZARD_INVALID_STATE |
API was called in wrong state |
WIZARD_ALREADY_COMPLETED |
Wizard is already completed |
WIZARD_STEP_INCOMPLETE |
Previous step missing |
DEPLOYMENT_FAILED |
Manifest could not be installed |
- User opens
/wizard - UI calls:
GET /wizard/status - Display current step
- User submits form data
- API saves config
- Wizard goes to next step
After step 5:
- Redirect to login page
4-page stepper:
- Admin
- Organization
- Connections
- Installation
Wizard is Fullscreen to avoid distractions.
The Deployment Engine is the central mechanism with which ReadyStackGo installs, updates, or validates a complete stack based on a release manifest. This chapter describes the complete internal logic.
The installation process runs in 10 steps:
- Load manifest
- Version and schema check
- Collect old container list
- Generate DeploymentPlan
- Ensure Docker network
- Execute context-wise actions
- Deploy gateway last
- Perform health checks (optional / later)
- Update release file
- Return result to API
The DeploymentPlan describes exactly all operations necessary to install the release. Example:
[
{ "type": "stop", "context": "project" },
{ "type": "remove", "context": "project" },
{ "type": "create", "context": "project" },
{ "type": "start", "context": "project" }
]- Each context is completely replaced → no in-place updates
- Gateway context always as last step
- Internal contexts first
- Exposed ports only on Gateway
Before each deployment, it is ensured that the network exists:
await _docker.NetworkEnsureExistsAsync(system.DockerNetwork);Name:
rsgo-net
All containers are started in it.
For each context, the Engine calls:
var env = await _envVarService.GenerateForContextAsync(contextName, manifest);This object contains:
RSGO_ORG_IDRSGO_STACK_VERSIONRSGO_FEATURE_*RSGO_CONNECTION_*- Manifest Overrides
Example:
{
"RSGO_ORG_ID": "customer-a",
"RSGO_CONNECTION_persistence": "Server=sql;Database=ams",
"ROUTE_PROJECT": "http://ams-project"
}Stops running containers.
await _docker.StopAsync(containerName);Completely removes containers.
await _docker.RemoveAsync(containerName);Creates container based on manifest.
await _docker.CreateAndStartAsync(new ContainerCreateModel {
Name = contextName,
Image = imageTag,
Env = envVars,
Network = network,
ExposedPorts = ports
});Starts container (if not auto-started).
await _docker.StartAsync(containerName);The Gateway context is special:
- gets TLS parameters
- is publicly accessible
- is therefore always deployed last
"gateway": {
"context": "edge-gateway",
"protocol": "https",
"publicPort": 8443,
"internalHttpPort": 8080
}The container is created with these ports:
- exposed: 8080
- published: 8443
Stop the deployment completely:
- Image cannot be loaded
- Container cannot be created
- Network error
- Manifest invalid
Error Codes:
| Code | Description |
|---|---|
DEPLOYMENT_FAILED |
General error |
DOCKER_NETWORK_ERROR |
Network could not be created |
CONTAINER_START_FAILED |
Container cannot start |
INVALID_MANIFEST |
Schema invalid |
Only warnings (visible later in UI):
- Health check not OK
- Container takes longer to start
After successful installation:
{
"installedStackVersion": "4.3.0",
"installedContexts": {
"project": "6.4.0",
"memo": "4.1.3",
"discussion": "3.5.9"
},
"installDate": "2025-04-12T10:22:00Z"
}Result:
{
"success": true,
"data": {
"installedVersion": "4.3.0"
}
}On error:
{
"success": false,
"errorCode": "DEPLOYMENT_FAILED",
"message": "Container 'project' could not be started."
}A manifest is the central file that describes the entire stack to be installed. This chapter defines the complete JSON Schema that ReadyStackGo uses for manifests.
A manifest consists of the following main elements:
{
"manifestVersion": "string",
"stackVersion": "string",
"schemaVersion": 1,
"releaseDate": "2025-03-01",
"gateway": { ... },
"contexts": { ... },
"features": { ... },
"metadata": { ... }
}{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ReadyStackGo Manifest",
"type": "object",
"required": [
"manifestVersion",
"stackVersion",
"schemaVersion",
"contexts"
],
"properties": {
"manifestVersion": { "type": "string" },
"stackVersion": { "type": "string" },
"schemaVersion": { "type": "number" },
"releaseDate": { "type": "string", "format": "date" },
"gateway": {
"type": "object",
"properties": {
"context": { "type": "string" },
"protocol": { "type": "string", "enum": ["http", "https"] },
"publicPort": { "type": "number" },
"internalHttpPort": { "type": "number" }
}
},
"contexts": {
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9_-]+$": {
"type": "object",
"required": ["image", "version", "containerName"],
"properties": {
"image": { "type": "string" },
"version": { "type": "string" },
"containerName": { "type": "string" },
"internal": { "type": "boolean" },
"dependsOn": {
"type": "array",
"items": { "type": "string" }
},
"env": {
"type": "object",
"additionalProperties": { "type": "string" }
},
"ports": {
"type": "array",
"items": {
"type": "object",
"properties": {
"private": { "type": "number" },
"public": { "type": ["number", "null"] },
"protocol": {
"type": "string",
"enum": ["tcp", "udp"]
}
}
}
}
}
}
}
},
"features": {
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9_-]+$": {
"type": "object",
"properties": {
"default": { "type": "boolean" },
"description": { "type": "string" }
}
}
}
},
"metadata": {
"type": "object",
"properties": {
"description": { "type": "string" },
"notes": { "type": "string" }
}
}
}
}{
"manifestVersion": "1.0.0",
"stackVersion": "4.3.0",
"schemaVersion": 12,
"releaseDate": "2025-03-01",
"gateway": {
"context": "edge-gateway",
"protocol": "https",
"publicPort": 8443,
"internalHttpPort": 8080
},
"contexts": {
"project": {
"image": "registry/ams.project-api",
"version": "6.4.0",
"containerName": "ams-project",
"internal": true,
"dependsOn": [],
"env": {},
"ports": []
},
"bffDesktop": {
"image": "registry/ams.bff-desktop",
"version": "1.3.0",
"containerName": "ams-bff-desktop",
"internal": false,
"dependsOn": ["project"],
"env": {
"ROUTE_PROJECT": "http://ams-project"
},
"ports": []
}
},
"features": {
"newColorTheme": { "default": true },
"discussionV2": { "default": false }
},
"metadata": {
"description": "Full AMS Release 4.3.0",
"notes": "This release contains the new dashboard."
}
}- schemaVersion only increases when manifest structure changed.
- Backwards compatibility is preserved where possible.
- Old manifests may still be installed.
- With incompatible versions, installation is refused:
{
"success": false,
"errorCode": "SCHEMA_INCOMPATIBLE"
}ReadyStackGo searches for manifests:
- /manifests in the Admin container
- later: via a registry (e.g., GitHub Releases, Azure DevOps Artifact Feed)
The name corresponds to the stack version:
manifest-4.3.0.json
manifest-4.4.1.json
ReadyStackGo integrates directly with the customer's Docker host. This happens exclusively via the Docker Socket, which is mounted as a volume into the Admin container:
-v /var/run/docker.sock:/var/run/docker.sock
This gives ReadyStackGo:
- full access to containers
- full access to images
- access to networks
- access to logs
- access to events
This is necessary to fully control stacks.
ReadyStackGo uses the official library:
<PackageReference Include="Docker.DotNet" Version="3.125.5" />This communicates directly with the Docker Socket via HTTP.
For each context container, the following steps are executed:
await client.Containers.StopContainerAsync(id, new ContainerStopParameters());await client.Containers.RemoveContainerAsync(id, new ContainerRemoveParameters { Force = true });await client.Containers.CreateContainerAsync(new CreateContainerParameters {
Image = model.Image,
Name = model.Name,
Env = model.Env.Select(kvp => $"{kvp.Key}={kvp.Value}").ToList(),
HostConfig = new HostConfig {
NetworkMode = model.Network,
RestartPolicy = new RestartPolicy { Name = model.RestartPolicy }
}
});await client.Containers.StartContainerAsync(id, null);await client.Networks.CreateNetworkAsync(new NetworksCreateParameters {
Name = "rsgo-net"
});If already present, it silently continues.
Internal ports are always set:
"ports": [
{ "private": 8080, "public": null, "protocol": "tcp" }
]Gateway additionally sets public ports:
"ports": [
{ "private": 8080, "public": 8443, "protocol": "tcp" }
]ReadyStackGo can later stream live logs:
await client.Containers.GetContainerLogsAsync(id, false, new ContainerLogsParameters {
ShowStdout = true,
ShowStderr = true,
Follow = true
});This is not part of version 1.0.
Docker Events enable:
- Detection of container crashes
- Monitoring
- Auto-Healing later
Will be built into a later version.
Access to the Docker Socket is potentially dangerous. Therefore important:
- Admin container is secured via HTTPS
- Login roles control access
- No direct shell access
- Containers cannot be exec'd
This chapter defines the complete contract between the React frontend (Tailwind + TailAdmin) and the ReadyStackGo API.
The UI works strictly typed via TypeScript interfaces that exactly match the API DTOs.
The UI:
- contains no logic that mutates system states
- only calls defined endpoints
- only reacts to the Wizard State Machine
- reads container status, release info, features, TLS info, etc.
The API contains 100% of the business logic.
All endpoints:
- path-based (
/api/v1/...) - return format: JSON
- errors as:
{
"success": false,
"errorCode": "XYZ",
"message": "Error description"
}UI must interpret errorCode, not message.
export interface ContainerDto {
id: string;
name: string;
image: string;
state: "running" | "exited" | "paused";
created: string; // ISO date
ports: PortMappingDto[];
}
export interface PortMappingDto {
private: number;
public: number | null;
protocol: "tcp" | "udp";
}export interface WizardStatusDto {
state:
| "NotStarted"
| "AdminCreated"
| "OrganizationSet"
| "ConnectionsSet"
| "Installed";
}export interface ReleaseStatusDto {
installedStackVersion: string;
installedContexts: Record<string, string>;
installDate: string;
}export interface TlsStatusDto {
tlsMode: "SelfSigned" | "Custom";
certificatePath: string;
httpEnabled: boolean;
httpsPort: number;
terminatingContext: string;
}export interface FeatureFlagsDto {
features: Record<string, boolean>;
}POST /api/v1/auth/login
Body:
{
username: string;
password: string;
}
Response:
{
success: true;
token: string;
}All Wizard calls have no return data except success.
Example:
POST /api/v1/wizard/admin
{
username: "admin",
password: "xyz123..."
}- Username
- Password
- POST /auth/login
- Store token in LocalStorage or Cookie
The UI calls:
/api/v1/containers/api/v1/releases/current
and shows:
- Container Status
- Stack Version
- Actions (admin only)
Actions:
- start/stop (operator, admin)
- logs (later)
- details
Actions:
- Load versions (
GET /releases) - Installation (
POST /releases/{version}/install)
- List of all features
- Toggle switch
- POST
/admin/features
- Display TLS status
- Certificate upload (PFX)
- POST
/admin/tls/upload
- Simple/Advanced Mode switch
- Global connections
- Context overrides
The frontend performs only minimal validation:
- Check required fields
- Format validation (e.g., Port = number)
- Show feedback on errors
All deeper rules lie in the API.
The UI checks:
if (!response.success) {
switch (response.errorCode) {
case "WIZARD_INVALID_STATE":
case "DEPLOYMENT_FAILED":
case "INVALID_MANIFEST":
showError(response.message);
break;
}
}- UI always shows the step based on
/wizard/status - no navigation by user possible
- no going back
- after installation → Redirect
/login
Recommendation:
- State via React Query / Zustand as "server state"
- minimal use of Redux or Context API
- UI is fully API-driven
This chapter describes the complete technical implementation of the security layer of ReadyStackGo.
ReadyStackGo supports two main modes:
- Local Authentication (Default)
- OpenID Connect – external Identity Provider (activatable later)
The first user (Admin) is created in the Wizard.
Data is stored in rsgo.security.json.
{
"username": "admin",
"passwordHash": "<base64>",
"passwordSalt": "<base64>"
}Recommended algorithm:
- PBKDF2-HMAC-SHA256
- Iterations: 210,000
- Salt: 16-32 bytes random
- Hash: 32-64 bytes
POST /api/v1/auth/login
{
"success": true,
"token": "<JWT>"
}Header:
{
"alg": "HS256",
"typ": "JWT"
}Claims:
{
"sub": "admin",
"role": "admin",
"exp": 1714579200
}Secret:
- stored locally in
rsgo.security.jsonor generated internally - exchangeable later via Admin UI
There are two roles:
| Role | Description |
|---|---|
| admin | Full access to all functions |
| operator | Can start/stop containers |
{
"roles": {
"admin": {
"canManageConfig": true,
"canDeploy": true,
"canRestartContainers": true
},
"operator": {
"canManageConfig": false,
"canDeploy": false,
"canRestartContainers": true
}
}
}Each endpoint defines roles explicitly:
public override void Configure()
{
Get("/api/containers");
Roles("admin", "operator");
}- no authentication
- not accessible after Wizard completion
ReadyStackGo can later be connected via OIDC to:
- Keycloak
- ams.identity
- Azure AD (later)
{
"externalProvider": {
"authority": "https://identity.local",
"clientId": "rsgo-admin-ui",
"clientSecret": "<secret>",
"adminRoleClaim": "role",
"adminRoleValue": "rsgo-admin",
"operatorRoleValue": "rsgo-operator"
}
}- UI → Redirect to IdP
- Login happens at IdP
- Token → ReadyStackGo
- Extract roles from claims
- Grant / deny access
Configurable:
{
"localAdminFallbackEnabled": true
}When enabled:
- If IdP is offline, local admin remains login-capable
- If disabled → only IdP allows logins
Gateway receives:
- Certificate
- HTTPS Port
- Exposed Port
Internally everything communicates via HTTP.
- can be accessible via HTTPS (for setup)
- terminates TLS on itself in Wizard mode
All responses contain:
X-Frame-Options: DENYX-Content-Type-Options: nosniffX-XSS-Protection: 1-
Strict-Transport-Security(if https)
When JWT via Cookie:
- UI sends X-CSRF-Header
- Server checks token in header and cookie
Currently: JWT via LocalStorage recommended.
Planned:
- Default: 100 requests/min per IP
- Admin endpoints more restrictive
This chapter describes the logging and monitoring concept of ReadyStackGo, as well as how errors are captured, stored, and provided to the UI.
ReadyStackGo uses by default:
- Microsoft.Extensions.Logging
- Output to Console
- Output to FileLog (optional, later)
- Structured Logs via Serilog (planned)
- Log level configurable
Standard log levels:
Information
Warning
Error
Critical
By default:
/app/logs/rsgo-admin.log
Rotating logs (planned):
rsgo-admin.logrsgo-admin.log.1rsgo-admin.log.2
During deployment:
- each step is logged
- errors are additionally written to a separate deployment log
- UI can later retrieve deployment logs
Example log entry:
[INFO] [Deployment] Starting context 'project' (image registry/ams.project-api:6.4.0)
[ERROR] [Docker] Failed to start container 'project': port already in use
Later the following API should exist:
GET /api/v1/containers/{id}/logs?follow=true
This streams:
- stdout
- stderr
An internal EventLog stores:
| Timestamp | Category | Event |
|---|---|---|
| 2025-03-11 08:12 | Deploy | Install stackVersion=4.3.0 |
| 2025-03-11 08:14 | TLS | Custom certificate uploaded |
| 2025-03-12 09:20 | Auth | Login failed for user admin |
API:
GET /api/v1/admin/events
Each error receives a unique code, e.g.:
| Code | Description |
|---|---|
DEPLOYMENT_FAILED |
Error in deployment process |
INVALID_MANIFEST |
Manifest faulty |
SCHEMA_INCOMPATIBLE |
Manifest schema not compatible |
WIZARD_INVALID_STATE |
Wizard was called in wrong phase |
DOCKER_NETWORK_ERROR |
Docker network error |
CONTAINER_START_FAILED |
Container could not start |
AUTH_FAILED |
Login invalid |
TLS_INVALID |
Certificate invalid |
Example for Deployment:
try
{
await _docker.StartAsync(contextName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to start container {Context}", contextName);
return new DeploymentResult {
Success = false,
Error = $"CONTAINER_START_FAILED: {ex.Message}"
};
}Planned:
- Regularly check
/healthendpoints of services - Display results in UI
- Optional alerts in Admin UI
This chapter describes the internal logic with which ReadyStackGo determines in what order containers are installed, removed, or updated.
This is crucial so that the stack can be rolled out deterministically, safely, and predictably.
ReadyStackGo follows these rules:
-
Each context is completely replaced → never "in-place updates", never diff-based changes.
-
Contexts without external ports first → internal APIs, workers, service bus listeners, EventStore, Identity, ...
-
Gateways always last → so that public endpoints only go online when internal services are running.
-
Start order follows dependencies (dependsOn) → e.g.: BFF → Project API → Identity → otherwise deterministic alphabetical sorting.
-
Errors stop the entire process → no "partially installed".
Algorithm in pseudocode:
contexts = manifest.contexts
internal = contexts where internal = true
external = contexts where internal = false
order_internal_by_dependencies(internal)
order_external_by_dependencies(external)
install_order = internal + external
gateway = manifest.gateway.context
move gateway to end
Manifest excerpt:
"contexts": {
"project": { "internal": true },
"identity": { "internal": true },
"bffDesktop": { "internal": false, "dependsOn": ["project","identity"] },
"edge-gateway": { "internal": false }
}Installation order:
- identity
- project
- bffDesktop
- edge-gateway
For each service, 4 steps are generated:
- stop
- remove
- create
- start
Example:
[
{ "type": "stop", "context": "identity" },
{ "type": "remove", "context": "identity" },
{ "type": "create", "context": "identity" },
{ "type": "start", "context": "identity" }
]Internal contexts:
- set no public ports
- set only "private" container ports (Exposed Ports)
Gateway:
- sets private port → internal HTTP port (e.g., 8080)
- sets public port → HTTPS port (e.g., 8443)
Before deployment, the following are checked:
- All container images available?
- Is the publicPort already in use?
- Schema version compatible?
- Connection strings working? (Basic Regex level)
- Gateway context exists?
The algorithm allows:
- direct dependencies (1 level)
- deep dependencies (multiple levels)
- cycles are detected and throw error:
errorCode: "MANIFEST_DEPENDENCY_CYCLE"
Potential optimizations:
- start internal services in parallel
- external services sequentially
- Gateway always after all others
This optimization is planned for later versions.
Error codes:
| Code | Meaning |
|---|---|
MANIFEST_DEPENDENCY_MISSING |
A dependsOn reference points to unknown context |
MANIFEST_DEPENDENCY_CYCLE |
A cyclic dependency was detected |
MANIFEST_GATEWAY_INVALID |
The defined gateway context does not exist |
Even though ReadyStackGo initially works Single-Node, the entire system is designed from the start so that an extended Multi-Node infrastructure can build upon it. This chapter describes the planned feature scope and technical requirements for future cluster capability.
- Distribution of individual contexts to different machines
-
Role-based node assignment
- Gateway Node
- Compute Node
- Storage Node
- Central management still via the Admin container
- No dependency on Kubernetes or Swarm
- Full offline capability
-
Extensible node configuration via
rsgo.nodes.json
{
"nodes": [
{
"nodeId": "local",
"name": "Local Node",
"dockerHost": "unix:///var/run/docker.sock",
"roles": ["default"],
"enabled": true
},
{
"nodeId": "remote-01",
"name": "Remote Server 01",
"dockerHost": "tcp://192.168.0.12:2375",
"roles": ["compute"],
"enabled": true
}
]
}| Role | Meaning |
|---|---|
default |
Standard node, everything may run on it |
gateway |
Node for edge-gateway and public API |
compute |
Node for compute-intensive contexts |
storage |
Node for e.g., eventstore, db-proxy etc. |
For each context in the manifest:
"contexts": {
"project": {
"nodeRole": "compute"
}
}The deployment algorithm does:
node = findNodeWithRole(context.nodeRole)
dockerService = GetDockerService(node)
deploy(context) on dockerService
Remote Nodes need:
- Docker Engine with activated TCP Listener or
- SSH Tunnel (planned)
- TLS secured connections
Example:
tcp://host:2376
Optional mechanisms:
- mDNS Autodiscovery
- Node heartbeat
- Cluster status display in UI
- Wizard supports only one node
- Node management only from v1.1
- No automatic load balancing
- No self-healing containers
This chapter describes how ReadyStackGo can be fully integrated into modern CI/CD pipelines (Azure DevOps, GitHub Actions, GitLab CI). This is essential for automated releases, pre-releases, and QA deployments.
- Automated builds of all context containers
- Automated tagging according to SemVer (x.y.z)
- Automated pushing to Docker Hub or own registry
- Automated creation of release manifest
- Automated provision of pre-releases
- Trigger for ReadyStackGo installations on development servers
Each microservice context (e.g., Project, Memo, Discussion) needs:
/build
Dockerfile
version.txt
version.txt contains:
6.4.0
- Read
version.txt - Increment patch version or release version automatically
docker build -t registry/ams.project-api:$(VERSION) .
docker push registry/ams.project-api:$(VERSION)
A script creates/updates:
manifest-$(STACK_VERSION).json
with new container versions.
- Manifest is published as build artifact
- Optionally copied directly to ReadyStackGo directory
Pre-release containers are tagged with:
6.4.0-alpha.1
6.4.0-beta.2
6.4.0-rc.1
Manifest can reference these versions, e.g.:
"version": "6.4.0-beta.2"Azure DevOps Pipeline can after successful build:
- call a Webhook URL of ReadyStackGo:
POST /api/v1/hooks/deploy
{ "version": "4.3.0-alpha.7" }
- ReadyStackGo loads the manifest
- Deployment starts automatically
This is optional and only possible in dev mode.
A PowerShell or Node.js script automatically generates:
manifest-<stackVersion>.jsonchangelogschemaVersion
The structure:
{
"stackVersion": "4.3.0",
"contexts": {
"project": { "version": "6.4.0" },
"memo": { "version": "4.1.3" }
}
}A QA server can provide a special webpage:
- "Deploy latest pre-release"
- "Deploy specific version"
- "Rollback last version"
This uses ReadyStackGo as backend.
- Access to registry via Service Connection
- Webhooks signed with Secret Token
- ReadyStackGo validates Origin
This chapter describes the complete error and return model for ReadyStackGo. The goal is a consistent, machine-readable definition that both UI and external tools like CI/CD can process unambiguously.
Every API response follows exactly this format:
{
"success": true,
"data": { ... },
"errorCode": null,
"message": null
}On errors:
{
"success": false,
"data": null,
"errorCode": "XYZ_ERROR",
"message": "Human-readable description"
}The UI interprets errorCode, not message.
These error codes are valid API-wide:
| Code | Meaning |
|---|---|
UNKNOWN_ERROR |
Fallback for unexpected errors |
INVALID_REQUEST |
Payload invalid, required fields missing |
UNAUTHORIZED |
No token present |
FORBIDDEN |
Role has no permission |
NOT_FOUND |
Resource does not exist |
OPERATION_NOT_ALLOWED |
Action not allowed in this state |
| Code | Description |
|---|---|
WIZARD_INVALID_STATE |
Step may not be executed currently |
WIZARD_ALREADY_COMPLETED |
Wizard already completed |
WIZARD_STEP_INCOMPLETE |
Previous step missing |
WIZARD_ORG_INVALID |
Organization invalid |
WIZARD_CONNECTIONS_INVALID |
Connection details invalid |
| Code | Description |
|---|---|
INVALID_MANIFEST |
JSON not parseable or structurally wrong |
MANIFEST_NOT_FOUND |
Version does not exist |
SCHEMA_INCOMPATIBLE |
Manifest schema too old/new |
MANIFEST_DEPENDENCY_MISSING |
dependsOn references unknown context |
MANIFEST_DEPENDENCY_CYCLE |
Circular dependency |
MANIFEST_GATEWAY_INVALID |
Gateway context missing or invalid |
| Code | Meaning |
|---|---|
DEPLOYMENT_FAILED |
General deployment error |
DOCKER_NETWORK_ERROR |
Network creation failed |
CONTAINER_CREATE_FAILED |
Container could not be created |
CONTAINER_START_FAILED |
Container could not be started |
IMAGE_PULL_FAILED |
Image could not be loaded |
| Code | Meaning |
|---|---|
TLS_INVALID |
Certificate invalid |
TLS_INSTALL_FAILED |
Upload/installation failed |
TLS_MODE_UNSUPPORTED |
Mode not supported |
| Code | Meaning |
|---|---|
AUTH_FAILED |
Wrong user or password |
OIDC_CONFIG_INVALID |
OIDC details invalid |
TOKEN_EXPIRED |
JWT expired |
TOKEN_INVALID |
JWT invalid or manipulated |
try
{
var result = await _dispatcher.SendAsync(new InstallStackCommand(version));
return TypedResults.Ok(Response.Success(result));
}
catch (InvalidManifestException ex)
{
_logger.LogWarning(ex, "Manifest invalid");
return TypedResults.BadRequest(Response.Error("INVALID_MANIFEST", ex.Message));
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error");
return TypedResults.Problem(Response.Error("UNKNOWN_ERROR", ex.Message));
}Example for TypeScript:
if (!res.success) {
switch (res.errorCode) {
case "INVALID_MANIFEST":
case "DEPLOYMENT_FAILED":
case "WIZARD_INVALID_STATE":
toast.error(res.message);
break;
default:
toast.error("An unexpected error occurred.");
}
}| HTTP Code | When? |
|---|---|
200 |
Success |
400 |
Client-side error (e.g., invalid manifest) |
401 |
No login |
403 |
Wrong role |
404 |
Resource not found |
500 |
Unexpected error |
This chapter describes how the ReadyStackGo Admin container is structured internally, how it starts, what processes run, and which modules call each other.
When the container starts, the following happens:
-
Configuration Bootstrap
- Check if
/app/config/rsgo.system.jsonexists - If not → Wizard mode
- Check if
-
TLS Bootstrap
- If no certificate exists
- → Generate Self-Signed
- → Create rsgo.tls.json
-
Build Dependency Injection
- DockerService
- ConfigStore
- TLSService
- ManifestProvider
- DeploymentEngine
- EnvVarService
-
Start API
- Initialize FastEndpoints
- Serve Static Files (React UI)
-
Start Wizard or Login
- Wizard UI if wizardState != Installed
- otherwise Admin Login UI
/app
/api
/ui
/manifests
/config <-- rsgo-config Volume
/certs
/logs
/var/run/docker.sock <-- Docker API Access
+-----------------------+
| ReadyStackGo (Admin) |
| - API |
| - Wizard |
| - TLS Engine |
| - Config Store |
| - Deployment Engine |
| - Manifest Loader |
+-----------+-----------+
|
| Docker Socket
v
+-------------------------------+
| Docker Engine (Host) |
| - Container Lifecycle Mgmt |
| - Networks |
| - Images |
+-------------------------------+
Implemented with:
- FastEndpoints
- Filters for Auth
- Global Error Middleware
- Logging
/api/v1/... --> Dispatcher --> Application --> Domain
Consists of:
- Commands
- Queries
- Handlers
- Policies (e.g., order, manifest logic)
Example structure:
Application/
Commands/
InstallStack/
StartContainer/
StopContainer/
Queries/
ListContainers/
GetReleaseStatus/
- purely object-oriented
- completely independent of the system
- no Docker dependencies
Example:
Domain/
Entities/
ReleaseStatus.cs
DeploymentPlan.cs
ValueObjects/
ContextId.cs
Contains implementations for:
- DockerService
- TlsService
- FileConfigStore
- ManifestProvider
Communication:
- DockerService → Docker.DotNet
- FileConfigStore → JSON files
- TLSService → System.Security.Cryptography
The Admin container contains the following background processes (planned):
- checks if new manifests are available
- loads new versions automatically (for pre-release mode)
- checks container status
- marks "unhealthy"
- API shows state
- manages log files in volume
After installations:
- Old containers → removed
- Old images → optionally removed
- Dangling volumes → optionally removed
Optional cleanup mode:
POST /api/v1/admin/system/cleanup
Admin container resource consumption:
- CPU: ~1-2% idle
- RAM: 100-150 MB
- Storage: depends on logs & config (~10 MB)
Deployment process can briefly use more CPU.
This chapter describes the complete TLS/SSL implementation of ReadyStackGo – including certificate creation, validation, exchange, and integration into the gateway context.
- TLS is configured centrally in ReadyStackGo.
- The Gateway context terminates TLS traffic.
- The Admin container uses TLS only in Wizard to ensure secure setup.
- Installation always starts with a Self-Signed certificate (Default).
- A Custom certificate can be imported later via the UI (PFX).
Example:
{
"mode": "SelfSigned",
"certificatePath": "/app/certs/selfsigned.pfx",
"certificatePassword": "r$go123!",
"httpsPort": 8443,
"terminatingContext": "edge-gateway"
}Explanation:
| Field | Meaning |
|---|---|
| mode | SelfSigned or Custom |
| certificatePath | Path to PFX file |
| certificatePassword | Password of PFX |
| httpsPort | Port where Gateway terminates TLS |
| terminatingContext | Context name of Gateway |
The Self-Signed certificate is generated on first start:
public async Task<TlsGenerateResult> GenerateSelfSignedAsync(string cn)
{
using var rsa = RSA.Create(4096);
var certReq = new CertificateRequest(
$"CN={cn}",
rsa,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1
);
certReq.CertificateExtensions.Add(
new X509BasicConstraintsExtension(false, false, 0, false));
var cert = certReq.CreateSelfSigned(
DateTimeOffset.UtcNow.AddDays(-1),
DateTimeOffset.UtcNow.AddYears(10));
var password = GeneratePassword();
File.WriteAllBytes("/app/certs/selfsigned.pfx", cert.Export(X509ContentType.Pfx, password));
return new TlsGenerateResult {
Path = "/app/certs/selfsigned.pfx",
Password = password
};
}UI sends a multipart request:
POST /api/v1/admin/tls/upload
Backend checks:
- Is file PFX?
- Password correct?
- Certificate valid?
- Contains private keys?
On success:
- File to
/app/certs/custom.pfx -
rsgo.tls.json→ mode = "Custom" - Gateway container is started with custom certificate on next installation
The Gateway context is described in the manifest like this:
"gateway": {
"context": "edge-gateway",
"protocol": "https",
"publicPort": 8443,
"internalHttpPort": 8080
}When creating the container, certificate files are mounted:
HostConfig = new HostConfig {
Binds = new List<string> {
"/app/config/rsgo.tls.json:/tls/config.json",
"/app/certs:/tls/certs"
}
}The Gateway reads:
/tls/config.json
/tls/certs/*
Switch from Self-Signed to Custom happens:
- Upload
- Validation
- Update rsgo.tls.json
- Next deployment uses custom certificate
No downtime, as certificate only becomes active on Gateway restart.
The Admin container checks:
- Expiration date
- Private key present
- KeyUsage = DigitalSignature + KeyEncipherment
- SAN entries present?
UI shows warnings:
Certificate expires in 23 days.
| Code | Description |
|---|---|
TLS_INVALID |
Certificate could not be validated |
TLS_NO_PRIVATE_KEY |
PFX contains no private key |
TLS_PASSWORD_WRONG |
Password for PFX wrong |
TLS_INSTALL_FAILED |
File could not be saved |
Planned:
- ACME Challenge via Gateway
- Domain validation
- Auto-Renewal
This chapter describes the complete configuration system of ReadyStackGo.
All configurations are centrally located in the rsgo-config volume, which is mounted when starting the Admin container:
-v rsgo-config:/app/config
/app/config
rsgo.system.json
rsgo.security.json
rsgo.tls.json
rsgo.contexts.json
rsgo.features.json
rsgo.release.json
rsgo.nodes.json (future)
custom-files/ (future)
Each file has a clearly defined purpose.
Central system configuration:
{
"wizardState": "Installed",
"dockerNetwork": "rsgo-net",
"stackVersion": "4.3.0"
}Fields:
-
wizardState→ controls Wizard -
dockerNetwork→ network name -
stackVersion→ installed version
Stores all security-relevant data:
{
"localUsers": [
{
"username": "admin",
"passwordHash": "base64",
"passwordSalt": "base64",
"role": "admin"
}
],
"jwtSecret": "base64",
"oidc": null
}Description in Block 18. Important:
{
"mode": "Custom",
"certificatePath": "/app/certs/custom.pfx",
"certificatePassword": "xyz",
"httpsPort": 8443,
"terminatingContext": "edge-gateway"
}Global and context-dependent connection parameters:
{
"global": {
"transport": "Server=sql;Database=transport;",
"persistence": "Server=sql;Database=persistence;",
"eventStore": null
},
"contexts": {
"project": {
"overrides": {
"transport": null,
"persistence": null,
"eventStore": null
}
}
},
"advancedMode": false
}UI shows:
- Simple Mode: only "global" visible
- Advanced Mode: "contexts" with overrides
Feature Flags:
{
"features": {
"newColorTheme": true,
"discussionV2": false
}
}Cross-context!
Stores the currently installed version:
{
"installedStackVersion": "4.3.0",
"installedContexts": {
"project": "6.4.0",
"memo": "4.1.3"
},
"installDate": "2025-03-12T10:22:00Z"
}For multi-node capability:
{
"nodes": [
{
"nodeId": "local",
"dockerHost": "unix:///var/run/docker.sock",
"roles": [ "default" ],
"enabled": true
}
]
}Concept:
- never patch, always replace complete file
- all writes via
IConfigStore.SaveAsync() - atomic writes (temp → replace)
The Admin UI loads all files via:
GET /api/v1/admin/config
Response:
{
"system": { ... },
"tls": { ... },
"contexts": { ... },
"features": { ... },
"release": { ... }
}Changes happen via dedicated endpoints.
- UI validates only basic structure
- API validates values (regex, required, constraints)
- Deployment refuses installation with invalid config
Planned:
GET /api/v1/admin/config/backup
POST /api/v1/admin/config/restore
Complete ZIP with all files.
This final chapter describes how ReadyStackGo can be extended in the future – modular, scalable, and open to customer-specific or community-driven extensions.
ReadyStackGo is not just a deployment tool, but a modular platform core that will support the following functions long-term:
- Multi-Node Cluster
- Auto-Healing
- High Availability
- Canary Deployments
- Blue/Green Deployments
- Organizations with multiple environments
- Plugin system for individual extensions
- Monitoring & Metrics
- API Gateway Routing Editor
ReadyStackGo will receive a plugin system that allows:
- adding own endpoints
- embedding own menus in UI
- executing own deployment steps
- providing additional context variables
- connecting external tools (EventStore, Grafana etc.)
/app/plugins
/PluginA/
plugin.json
plugin.dll
/PluginB/
plugin.json
{
"name": "ProjectInsights",
"version": "1.0.0",
"author": "YourCompany",
"startupClass": "ProjectInsights.PluginStartup",
"ui": {
"menuLabel": "Insights",
"route": "/insights"
}
}public class ProjectInsightsPlugin : IRsgoPlugin
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IInsightsProvider, DefaultInsightsProvider>();
}
public void ConfigureApi(IEndpointRouteBuilder endpoints)
{
endpoints.MapGet("/api/insights", async context => {
// ...
});
}
}- Scans
/app/plugins - Loads assemblies
- Finds all classes implementing
IRsgoPlugin - Executes
ConfigureServices - Executes
ConfigureApi - UI automatically loads additional menu items
Later possible:
- Pre-Deployment Hooks
- Post-Deployment Hooks
- Custom Health Checks
- Custom EnvVar Provider
Example:
{
"hooks": {
"beforeCreate": "ProjectInsights.Hooks.ValidateBeforeCreate",
"afterStart": "ProjectInsights.Hooks.NotifyTeams"
}
}Planned:
- Integration of Prometheus
- Integration of Grafana Dashboards
- EventStore Monitoring
- Container Health Dashboard
Data points:
- CPU/RAM per container
- Start time
- Crash count
- Restart count
- Deployment duration
Later it should be possible, per organization:
- to define multiple environments
- to have own releases per environment
Example:
/orgs/customer-a/dev
/orgs/customer-a/test
/orgs/customer-a/prod
Each environment has its own:
- TLS settings
- Manifests
- Contexts
- Feature flags
New Wizard steps are conceivable:
- Environment Setup (dev/test/prod)
- Node Discovery
- Storage Setup (SQL, EventStore, Redis)
- License Management
Possible future features:
Containers are not completely deleted, but automatically rolled back after failed deployment.
- two partitions ("blue" and "green")
- Gateway switches between them
- small percentage of traffic goes to new version
- Monitoring decides on release
New modules can be created:
- Routing Editor for Gateway
- Live Logs
- System Dashboard
- Cluster Topology Visualization
- Audit Logs
Planned:
-
rsgo.nodes.json→ Multi-Node -
rsgo.environments.json→ dev/test/prod -
rsgo.plugins.json→ Plugin Management -
rsgo.metrics.json→ Metrics Configuration
ReadyStackGo is already fully functional as version 1.0, but is also designed as a platform for long-term extensions.
This solid foundation makes it possible that:
- Claude
- you yourself
- your team
- the community
can develop ReadyStackGo into a complete on-premises container platform – comparable to Portainer, but tailored to your microservice architecture.
Technical Specification complete!
Getting Started
Architecture
Configuration
Security
Setup Wizard
Development
Operations
CI/CD
Reference
- Roadmap
- API Reference
- Configuration Reference
- Manifest Schema
- Multi-Environment
- Stack Sources
- Plugin System
- Technical Specification
- Full Specification
Specifications
Release Notes