diff --git a/README.md b/README.md index 65bb188e..812ffb88 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ foundryctl cast -f casting.yaml | - | systemd | binary | [systemd/binary](docs/examples/systemd/binary/) | | ecs | ec2 | terraform | [ecs/ec2/terraform](docs/examples/ecs/ec2/terraform/) | | coolify | - | stack | [coolify/stack](docs/examples/coolify/stack/) | +| dokploy | - | stack | [dokploy/stack](docs/examples/dokploy/stack/) | | railway | - | template | [railway/template](docs/examples/railway/template/) | | render | - | blueprint | [render/blueprint](docs/examples/render/blueprint/) | diff --git a/api/v1alpha1/collectionagent/casting.schema.json b/api/v1alpha1/collectionagent/casting.schema.json index 8998b545..318eba81 100644 --- a/api/v1alpha1/collectionagent/casting.schema.json +++ b/api/v1alpha1/collectionagent/casting.schema.json @@ -244,6 +244,7 @@ "enum": [ "render", "coolify", + "dokploy", "railway", "ecs", "aws", diff --git a/api/v1alpha1/deployment_platform.go b/api/v1alpha1/deployment_platform.go index 7660bf35..f1371973 100644 --- a/api/v1alpha1/deployment_platform.go +++ b/api/v1alpha1/deployment_platform.go @@ -19,6 +19,7 @@ var _ jsonschema.Enum = (*Platform)(nil) var ( PlatformRender Platform = Platform{s: "render"} PlatformCoolify Platform = Platform{s: "coolify"} + PlatformDokploy Platform = Platform{s: "dokploy"} PlatformRailway Platform = Platform{s: "railway"} PlatformECS Platform = Platform{s: "ecs"} PlatformAWS Platform = Platform{s: "aws"} @@ -38,6 +39,7 @@ func Platforms() []Platform { return []Platform{ PlatformRender, PlatformCoolify, + PlatformDokploy, PlatformRailway, PlatformECS, PlatformAWS, diff --git a/api/v1alpha1/deployment_platform_test.go b/api/v1alpha1/deployment_platform_test.go new file mode 100644 index 00000000..dbc66a1b --- /dev/null +++ b/api/v1alpha1/deployment_platform_test.go @@ -0,0 +1,14 @@ +package v1alpha1 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPlatformDokployIsAvailable(t *testing.T) { + var platform Platform + require.NoError(t, platform.UnmarshalText([]byte("dokploy"))) + require.Equal(t, "dokploy", platform.String()) + require.Contains(t, Platforms(), PlatformDokploy) +} diff --git a/api/v1alpha1/installation/casting.schema.json b/api/v1alpha1/installation/casting.schema.json index c41aa1f1..92df216b 100644 --- a/api/v1alpha1/installation/casting.schema.json +++ b/api/v1alpha1/installation/casting.schema.json @@ -631,6 +631,7 @@ "enum": [ "render", "coolify", + "dokploy", "railway", "ecs", "aws", diff --git a/docs/examples/README.md b/docs/examples/README.md index d44789c5..cf152c75 100644 --- a/docs/examples/README.md +++ b/docs/examples/README.md @@ -9,5 +9,6 @@ | - | systemd | binary | [systemd/binary](systemd/binary/) | | ecs | ec2 | terraform | [ecs/ec2/terraform](ecs/ec2/terraform/) | | coolify | - | stack | [coolify/stack](coolify/stack/) | +| dokploy | - | stack | [dokploy/stack](dokploy/stack/) | | railway | - | template | [railway/template](railway/template/) | | render | - | blueprint | [render/blueprint](render/blueprint/) | diff --git a/docs/examples/castings.json b/docs/examples/castings.json index ded67711..22af8318 100644 --- a/docs/examples/castings.json +++ b/docs/examples/castings.json @@ -42,6 +42,12 @@ "flavor": "stack", "example": "coolify/stack" }, + { + "platform": "dokploy", + "mode": "", + "flavor": "stack", + "example": "dokploy/stack" + }, { "platform": "railway", "mode": "", diff --git a/docs/examples/dokploy/README.md b/docs/examples/dokploy/README.md new file mode 100644 index 00000000..c7f4a55b --- /dev/null +++ b/docs/examples/dokploy/README.md @@ -0,0 +1,5 @@ +# Dokploy + +| Flavor | Description | +| --- | --- | +| [stack](stack/) | Deploy SigNoz on Dokploy as a Docker Swarm stack | diff --git a/docs/examples/dokploy/stack/README.md b/docs/examples/dokploy/stack/README.md new file mode 100644 index 00000000..7a43532b --- /dev/null +++ b/docs/examples/dokploy/stack/README.md @@ -0,0 +1,62 @@ +# Dokploy Stack + +| Field | Value | +| --- | --- | +| **Mode** | `-` | +| **Flavor** | `stack` | +| **Platform** | `dokploy` | + +## Overview + +Generates a Docker Swarm `compose.yaml` and component configuration files for deployment through Dokploy's stack feature. + +> [!NOTE] +> `foundryctl cast` does not deploy to Dokploy automatically. It generates the files and prints instructions for manual deployment. + +## Prerequisites + +- A [Dokploy](https://dokploy.com) instance +- The external `dokploy-network` created by Dokploy + +## Configuration + +```yaml +apiVersion: v1alpha1 +metadata: + name: signoz +spec: + deployment: + flavor: stack + platform: dokploy +``` + +## Deploy + +```bash +foundryctl forge -f casting.yaml +``` + +Set `SIGNOZ_DOMAIN` in Dokploy to the hostname that should serve the SigNoz UI, then deploy `pours/deployment/compose.yaml` with the stack feature. The generated Swarm labels configure HTTPS routing through Dokploy's Traefik service on container port `8080`. The SigNoz UI is the only service attached to Dokploy's shared network; databases remain isolated on the stack's private overlay network. + +The ingester ports are exposed to container networks but are not published directly on the host. Connect collectors running in the Swarm to the stack's private overlay network. To receive telemetry from outside the Swarm, use a patch to attach the ingester to an appropriate ingress network and configure routing for port `4318` (OTLP/HTTP). + +## Generated output + +```text +pours/deployment/ + compose.yaml + ingester/ + ingester.yaml + opamp.yaml + telemetrykeeper/ + clickhousekeeper/ + keeper-0.yaml + telemetrystore/ + clickhouse/ + config-0-0.yaml + functions.yaml +``` + +## Customization + +For changes to the generated `compose.yaml`, use [patches](../../../concepts/patches.md). diff --git a/docs/examples/dokploy/stack/casting.yaml b/docs/examples/dokploy/stack/casting.yaml new file mode 100644 index 00000000..ff1568c2 --- /dev/null +++ b/docs/examples/dokploy/stack/casting.yaml @@ -0,0 +1,8 @@ +apiVersion: v1alpha1 +kind: Installation +metadata: + name: signoz +spec: + deployment: + flavor: stack + platform: dokploy diff --git a/docs/examples/dokploy/stack/casting.yaml.lock b/docs/examples/dokploy/stack/casting.yaml.lock new file mode 100644 index 00000000..2a612ce1 --- /dev/null +++ b/docs/examples/dokploy/stack/casting.yaml.lock @@ -0,0 +1,660 @@ +apiVersion: v1alpha1 +kind: Installation +metadata: + name: signoz +spec: + deployment: + flavor: stack + platform: dokploy + ingester: + spec: + cluster: + replicas: 1 + config: + data: + ingester.yaml: | + connectors: + signozmeter: + metrics_flush_interval: 1h + dimensions: + - name: service.name + - name: deployment.environment + - name: host.name + receivers: + otlp: + protocols: + grpc: + endpoint: "0.0.0.0:4317" + http: + endpoint: "0.0.0.0:4318" + processors: + batch: + send_batch_size: 50000 + send_batch_max_size: 55000 + timeout: 5s + batch/meter: + send_batch_size: 20000 + send_batch_max_size: 25000 + timeout: 5s + signozspanmetrics/delta: + metrics_exporter: signozclickhousemetrics + metrics_flush_interval: 60s + latency_histogram_buckets: + - 100us + - 1ms + - 2ms + - 6ms + - 10ms + - 50ms + - 100ms + - 250ms + - 500ms + - 1000ms + - 1400ms + - 2000ms + - 5s + - 10s + - 20s + - 40s + - 60s + dimensions_cache_size: 100000 + aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA + enable_exp_histogram: true + dimensions: + - name: service.namespace + default: default + - name: deployment.environment + default: default + - name: signoz.collector.id + - name: service.version + exporters: + clickhousetraces: + datasource: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_traces + low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING} + use_new_schema: true + timeout: 45s + sending_queue: + enabled: false + signozclickhousemetrics: + dsn: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_metrics + timeout: 45s + sending_queue: + enabled: false + clickhouselogsexporter: + dsn: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_logs + use_new_schema: true + timeout: 45s + sending_queue: + enabled: false + signozclickhousemeter: + dsn: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_meter + timeout: 45s + sending_queue: + enabled: false + metadataexporter: + enabled: true + dsn: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_metadata + timeout: 45s + cache: + provider: in_memory + extensions: + signoz_health_check: + endpoint: "0.0.0.0:13133" + pprof: + endpoint: "0.0.0.0:1777" + service: + telemetry: + logs: + encoding: json + extensions: + - signoz_health_check + - pprof + pipelines: + traces: + receivers: + - otlp + processors: + - signozspanmetrics/delta + - batch + exporters: + - clickhousetraces + - signozmeter + - metadataexporter + metrics: + receivers: + - otlp + processors: + - batch + exporters: + - signozclickhousemetrics + - signozmeter + - metadataexporter + logs: + receivers: + - otlp + processors: + - batch + exporters: + - clickhouselogsexporter + - signozmeter + - metadataexporter + metrics/meter: + receivers: + - signozmeter + processors: + - batch/meter + exporters: + - signozclickhousemeter + opamp.yaml: | + server_endpoint: ws://signoz-signoz-0:4320/v1/opamp + enabled: true + env: + SIGNOZ_OTEL_COLLECTOR_TIMEOUT: 10m + image: signoz/signoz-otel-collector:latest + version: latest + status: + addresses: + otlp: + - tcp://signoz-ingester:4318 + - tcp://signoz-ingester:4317 + config: + data: + ingester.yaml: | + connectors: + signozmeter: + metrics_flush_interval: 1h + dimensions: + - name: service.name + - name: deployment.environment + - name: host.name + receivers: + otlp: + protocols: + grpc: + endpoint: "0.0.0.0:4317" + http: + endpoint: "0.0.0.0:4318" + processors: + batch: + send_batch_size: 50000 + send_batch_max_size: 55000 + timeout: 5s + batch/meter: + send_batch_size: 20000 + send_batch_max_size: 25000 + timeout: 5s + signozspanmetrics/delta: + metrics_exporter: signozclickhousemetrics + metrics_flush_interval: 60s + latency_histogram_buckets: + - 100us + - 1ms + - 2ms + - 6ms + - 10ms + - 50ms + - 100ms + - 250ms + - 500ms + - 1000ms + - 1400ms + - 2000ms + - 5s + - 10s + - 20s + - 40s + - 60s + dimensions_cache_size: 100000 + aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA + enable_exp_histogram: true + dimensions: + - name: service.namespace + default: default + - name: deployment.environment + default: default + - name: signoz.collector.id + - name: service.version + exporters: + clickhousetraces: + datasource: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_traces + low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING} + use_new_schema: true + timeout: 45s + sending_queue: + enabled: false + signozclickhousemetrics: + dsn: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_metrics + timeout: 45s + sending_queue: + enabled: false + clickhouselogsexporter: + dsn: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_logs + use_new_schema: true + timeout: 45s + sending_queue: + enabled: false + signozclickhousemeter: + dsn: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_meter + timeout: 45s + sending_queue: + enabled: false + metadataexporter: + enabled: true + dsn: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_metadata + timeout: 45s + cache: + provider: in_memory + extensions: + signoz_health_check: + endpoint: "0.0.0.0:13133" + pprof: + endpoint: "0.0.0.0:1777" + service: + telemetry: + logs: + encoding: json + extensions: + - signoz_health_check + - pprof + pipelines: + traces: + receivers: + - otlp + processors: + - signozspanmetrics/delta + - batch + exporters: + - clickhousetraces + - signozmeter + - metadataexporter + metrics: + receivers: + - otlp + processors: + - batch + exporters: + - signozclickhousemetrics + - signozmeter + - metadataexporter + logs: + receivers: + - otlp + processors: + - batch + exporters: + - clickhouselogsexporter + - signozmeter + - metadataexporter + metrics/meter: + receivers: + - signozmeter + processors: + - batch/meter + exporters: + - signozclickhousemeter + opamp.yaml: | + server_endpoint: ws://signoz-signoz-0:4320/v1/opamp + env: + SIGNOZ_OTEL_COLLECTOR_TIMEOUT: 10m + mcp: + spec: + cluster: + replicas: 1 + config: {} + enabled: false + image: signoz/signoz-mcp-server:latest + version: latest + status: + addresses: + http: null + config: {} + metastore: + kind: postgres + spec: + cluster: + replicas: 1 + config: {} + enabled: true + env: + POSTGRES_DB: signoz + POSTGRES_PASSWORD: signoz + POSTGRES_USER: signoz + image: postgres:16 + version: "16" + status: + addresses: + dsn: + - tcp://signoz-metastore-postgres-0:5432 + config: {} + env: + POSTGRES_DB: signoz + POSTGRES_PASSWORD: signoz + POSTGRES_USER: signoz + signoz: + spec: + cluster: + replicas: 1 + config: {} + enabled: true + env: + SIGNOZ_SQLSTORE_POSTGRES_DSN: postgres://signoz:signoz@signoz-metastore-postgres-0:5432/signoz?sslmode=disable + SIGNOZ_SQLSTORE_PROVIDER: postgres + SIGNOZ_TELEMETRYSTORE_CLICKHOUSE_DSN: tcp://signoz-telemetrystore-clickhouse-0-0:9000 + SIGNOZ_TELEMETRYSTORE_PROVIDER: clickhouse + image: signoz/signoz:latest + version: latest + status: + addresses: + apiserver: + - tcp://signoz-signoz-0:8080 + opamp: + - ws://signoz-signoz-0:4320 + config: {} + env: + SIGNOZ_SQLSTORE_POSTGRES_DSN: postgres://signoz:signoz@signoz-metastore-postgres-0:5432/signoz?sslmode=disable + SIGNOZ_SQLSTORE_PROVIDER: postgres + SIGNOZ_TELEMETRYSTORE_CLICKHOUSE_DSN: tcp://signoz-telemetrystore-clickhouse-0-0:9000 + SIGNOZ_TELEMETRYSTORE_PROVIDER: clickhouse + telemetrykeeper: + kind: clickhousekeeper + spec: + cluster: + replicas: 1 + config: + data: + keeper-0.yaml: | + listen_host: 0.0.0.0 + logger: + level: information + console: true + keeper_server: + four_letter_word_white_list: "*" + coordination_settings: + operation_timeout_ms: 10000 + raft_logs_level: warning + session_timeout_ms: 30000 + force_sync: false + snapshot_distance: 100000 + snapshots_to_keep: 3 + log_storage_path: /var/lib/clickhouse-keeper/coordination/log + raft_configuration: + server: + - hostname: signoz-telemetrykeeper-clickhousekeeper-0 + port: 9234 + id: 0 + server_id: 0 + snapshot_storage_path: /var/lib/clickhouse-keeper/coordination/snapshots + tcp_port: 9181 + enabled: true + image: clickhouse/clickhouse-keeper:25.12.5 + version: 25.12.5 + status: + addresses: + client: + - tcp://signoz-telemetrykeeper-clickhousekeeper-0:9181 + raft: + - tcp://signoz-telemetrykeeper-clickhousekeeper-0:9234 + config: + data: + keeper-0.yaml: | + listen_host: 0.0.0.0 + logger: + level: information + console: true + keeper_server: + four_letter_word_white_list: "*" + coordination_settings: + operation_timeout_ms: 10000 + raft_logs_level: warning + session_timeout_ms: 30000 + force_sync: false + snapshot_distance: 100000 + snapshots_to_keep: 3 + log_storage_path: /var/lib/clickhouse-keeper/coordination/log + raft_configuration: + server: + - hostname: signoz-telemetrykeeper-clickhousekeeper-0 + port: 9234 + id: 0 + server_id: 0 + snapshot_storage_path: /var/lib/clickhouse-keeper/coordination/snapshots + tcp_port: 9181 + telemetrystore: + kind: clickhouse + spec: + cluster: + replicas: 0 + shards: 1 + config: + data: + config-0-0.yaml: | + path: /var/lib/clickhouse/ + tmp_path: /var/lib/clickhouse/tmp/ + user_files_path: /var/lib/clickhouse/user_files/ + format_schema_path: /var/lib/clickhouse/format_schemas/ + dictionaries_config: '*_dictionary.xml' + display_name: cluster + distributed_ddl: + path: /clickhouse/task_queue/ddl + http_port: 8123 + interserver_http_port: 9009 + listen_host: 0.0.0.0 + logger: + console: 1 + count: 10 + formatting: + type: console + level: information + size: 1000M + macros: + replica: "00" + shard: "00" + profiles: + default: + allow_simdjson: 0 + load_balancing: random + log_queries: 1 + quotas: + default: + interval: + duration: 3600 + errors: 0 + execution_time: 0 + queries: 0 + read_rows: 0 + result_rows: 0 + user_directories: + users_xml: + path: config-0-0.yaml + remote_servers: + cluster: + shard: + - replica: + - host: signoz-telemetrystore-clickhouse-0-0 + port: 9000 + zookeeper: + node: + - host: signoz-telemetrykeeper-clickhousekeeper-0 + port: 9181 + query_log: + flush_interval_milliseconds: 30000 + partition_by: toYYYYMM(event_date) + ttl: "event_date + INTERVAL 1 DAY DELETE" + query_thread_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + query_metric_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + query_views_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + part_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + metric_log: + flush_interval_milliseconds: 30000 + ttl: "event_date + INTERVAL 1 DAY DELETE" + asynchronous_metric_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + trace_log: + flush_interval_milliseconds: 30000 + ttl: "event_date + INTERVAL 1 DAY DELETE" + error_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + latency_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + processors_profile_log: + flush_interval_milliseconds: 30000 + ttl: "event_date + INTERVAL 1 DAY DELETE" + session_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + text_log: + flush_interval_milliseconds: 30000 + ttl: "event_date + INTERVAL 1 DAY DELETE" + zookeeper_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + tcp_port: 9000 + user_defined_executable_functions_config: '*functions.yaml' + user_scripts_path: /var/lib/clickhouse/user_scripts/ + users: + default: + access_management: 1 + named_collection_control: 1 + networks: + ip: ::/0 + password: "" + profile: default + quota: default + show_named_collection: 1 + show_named_collection_secrets: 1 + functions.yaml: | + functions: + argument: + - name: buckets + type: Array(Float64) + - name: counts + type: Array(Float64) + - name: quantile + type: Float64 + command: ./histogramQuantile + format: CSV + name: histogramQuantile + return_type: Float64 + type: executable + enabled: true + image: clickhouse/clickhouse-server:25.12.5 + version: 25.12.5 + status: + addresses: + tcp: + - tcp://signoz-telemetrystore-clickhouse-0-0:9000 + config: + data: + config-0-0.yaml: | + path: /var/lib/clickhouse/ + tmp_path: /var/lib/clickhouse/tmp/ + user_files_path: /var/lib/clickhouse/user_files/ + format_schema_path: /var/lib/clickhouse/format_schemas/ + dictionaries_config: '*_dictionary.xml' + display_name: cluster + distributed_ddl: + path: /clickhouse/task_queue/ddl + http_port: 8123 + interserver_http_port: 9009 + listen_host: 0.0.0.0 + logger: + console: 1 + count: 10 + formatting: + type: console + level: information + size: 1000M + macros: + replica: "00" + shard: "00" + profiles: + default: + allow_simdjson: 0 + load_balancing: random + log_queries: 1 + quotas: + default: + interval: + duration: 3600 + errors: 0 + execution_time: 0 + queries: 0 + read_rows: 0 + result_rows: 0 + user_directories: + users_xml: + path: config-0-0.yaml + remote_servers: + cluster: + shard: + - replica: + - host: signoz-telemetrystore-clickhouse-0-0 + port: 9000 + zookeeper: + node: + - host: signoz-telemetrykeeper-clickhousekeeper-0 + port: 9181 + query_log: + flush_interval_milliseconds: 30000 + partition_by: toYYYYMM(event_date) + ttl: "event_date + INTERVAL 1 DAY DELETE" + query_thread_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + query_metric_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + query_views_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + part_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + metric_log: + flush_interval_milliseconds: 30000 + ttl: "event_date + INTERVAL 1 DAY DELETE" + asynchronous_metric_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + trace_log: + flush_interval_milliseconds: 30000 + ttl: "event_date + INTERVAL 1 DAY DELETE" + error_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + latency_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + processors_profile_log: + flush_interval_milliseconds: 30000 + ttl: "event_date + INTERVAL 1 DAY DELETE" + session_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + text_log: + flush_interval_milliseconds: 30000 + ttl: "event_date + INTERVAL 1 DAY DELETE" + zookeeper_log: + ttl: "event_date + INTERVAL 1 DAY DELETE" + tcp_port: 9000 + user_defined_executable_functions_config: '*functions.yaml' + user_scripts_path: /var/lib/clickhouse/user_scripts/ + users: + default: + access_management: 1 + named_collection_control: 1 + networks: + ip: ::/0 + password: "" + profile: default + quota: default + show_named_collection: 1 + show_named_collection_secrets: 1 + functions.yaml: | + functions: + argument: + - name: buckets + type: Array(Float64) + - name: counts + type: Array(Float64) + - name: quantile + type: Float64 + command: ./histogramQuantile + format: CSV + name: histogramQuantile + return_type: Float64 + type: executable diff --git a/docs/examples/dokploy/stack/pours/deployment/compose.yaml b/docs/examples/dokploy/stack/pours/deployment/compose.yaml new file mode 100644 index 00000000..62df3eb1 --- /dev/null +++ b/docs/examples/dokploy/stack/pours/deployment/compose.yaml @@ -0,0 +1,209 @@ +configs: + ingester-config: + file: ingester/ingester.yaml + manager-config: + file: ingester/opamp.yaml + telemetrykeeper-0: + file: telemetrykeeper/clickhousekeeper/keeper-0.yaml + telemetrystore-config-0-0: + file: telemetrystore/clickhouse/config-0-0.yaml + telemetrystore-functions: + file: telemetrystore/clickhouse/functions.yaml +networks: + dokploy-network: + external: true + name: dokploy-network + signoz-network: + attachable: true + driver: overlay + name: signoz-network +services: + signoz-ingester: + command: + - -c + - | + /signoz-otel-collector migrate sync check && + /signoz-otel-collector --config=/etc/otel-collector-config.yaml --manager-config=/etc/opamp-config.yaml --copy-path=/var/tmp/collector-config.yaml + configs: + - source: ingester-config + target: /etc/otel-collector-config.yaml + - source: manager-config + target: /etc/opamp-config.yaml + deploy: + replicas: 1 + entrypoint: + - /bin/sh + environment: + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://signoz-telemetrystore-clickhouse-0-0:9000 + - SIGNOZ_OTEL_COLLECTOR_TIMEOUT=10m + expose: + - "4317" + - "4318" + image: signoz/signoz-otel-collector:latest + networks: + signoz-network: + aliases: + - signoz-ingester + signoz-metastore-postgres-0: + deploy: + restart_policy: + condition: on-failure + environment: + - POSTGRES_DB=signoz + - POSTGRES_PASSWORD=signoz + - POSTGRES_USER=signoz + healthcheck: + interval: 30s + retries: 3 + start_period: 30s + test: + - CMD-SHELL + - pg_isready -U signoz -d signoz + timeout: 10s + image: postgres:16 + networks: + - signoz-network + volumes: + - signoz-metastore-postgres-0-data:/var/lib/postgresql/data + signoz-signoz-0: + deploy: + labels: + - traefik.enable=true + - traefik.docker.network=dokploy-network + - traefik.http.routers.signoz-signoz.rule=Host(`${SIGNOZ_DOMAIN:?SIGNOZ_DOMAIN_required}`) + - traefik.http.routers.signoz-signoz.entrypoints=websecure + - traefik.http.routers.signoz-signoz.tls.certresolver=letsencrypt + - traefik.http.routers.signoz-signoz.service=signoz-signoz + - traefik.http.services.signoz-signoz.loadbalancer.server.port=8080 + restart_policy: + condition: on-failure + environment: + - SIGNOZ_SQLSTORE_POSTGRES_DSN=postgres://signoz:signoz@signoz-metastore-postgres-0:5432/signoz?sslmode=disable + - SIGNOZ_SQLSTORE_PROVIDER=postgres + - SIGNOZ_TELEMETRYSTORE_CLICKHOUSE_DSN=tcp://signoz-telemetrystore-clickhouse-0-0:9000 + - SIGNOZ_TELEMETRYSTORE_PROVIDER=clickhouse + expose: + - "8080" + healthcheck: + interval: 30s + retries: 3 + start_period: 60s + test: + - CMD + - wget + - --spider + - -q + - http://localhost:8080/api/v1/health + timeout: 10s + image: signoz/signoz:latest + networks: + - signoz-network + - dokploy-network + signoz-telemetrykeeper-clickhousekeeper-0: + configs: + - source: telemetrykeeper-0 + target: /etc/clickhouse-keeper/keeper.yaml + deploy: + restart_policy: + condition: on-failure + entrypoint: + - /usr/bin/clickhouse-keeper + - --config-file=/etc/clickhouse-keeper/keeper.yaml + healthcheck: + interval: 30s + retries: 3 + start_period: 40s + test: + - CMD + - clickhouse-keeper-client + - -h + - localhost + - -p + - "9181" + - -q + - ls + timeout: 10s + image: clickhouse/clickhouse-keeper:25.12.5 + networks: + - signoz-network + volumes: + - signoz-telemetrykeeper-0-data:/var/lib/clickhouse-keeper + signoz-telemetrystore-clickhouse-0-0: + configs: + - source: telemetrystore-config-0-0 + target: /etc/clickhouse-server/config-0-0.yaml + - source: telemetrystore-functions + target: /etc/clickhouse-server/functions.yaml + deploy: + restart_policy: + condition: on-failure + environment: + - CLICKHOUSE_SKIP_USER_SETUP=1 + - CLICKHOUSE_CONFIG=/etc/clickhouse-server/config-0-0.yaml + healthcheck: + interval: 30s + retries: 3 + start_period: 40s + test: + - CMD + - wget + - --spider + - -q + - http://localhost:8123/ping + timeout: 10s + hostname: signoz-telemetrystore-clickhouse-0-0 + image: clickhouse/clickhouse-server:25.12.5 + networks: + - signoz-network + volumes: + - signoz-telemetrystore-0-0-data:/var/lib/clickhouse + - signoz-telemetrystore-user-scripts:/var/lib/clickhouse/user_scripts:ro + signoz-telemetrystore-clickhouse-user-scripts: + command: + - bash + - -c + - |- + version="v0.0.1" + node_os=$$(uname -s | tr '[:upper:]' '[:lower:]') + node_arch=$$(uname -m | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) + echo "Fetching histogram-binary for $${node_os}/$${node_arch}" + cd /tmp + wget -O histogram-quantile.tar.gz "https://github.com/SigNoz/signoz/releases/download/histogram-quantile%2F$${version}/histogram-quantile_$${node_os}_$${node_arch}.tar.gz" + tar -xvzf histogram-quantile.tar.gz + mv histogram-quantile /var/lib/clickhouse/user_scripts/histogramQuantile + deploy: + mode: global + restart_policy: + condition: none + image: clickhouse/clickhouse-server:25.12.5 + networks: + - signoz-network + volumes: + - signoz-telemetrystore-user-scripts:/var/lib/clickhouse/user_scripts + signoz-telemetrystore-migrator: + command: + - -c + - | + /signoz-otel-collector migrate ready && + /signoz-otel-collector migrate bootstrap && + /signoz-otel-collector migrate sync up && + /signoz-otel-collector migrate async up + deploy: + replicas: 1 + restart_policy: + condition: on-failure + max_attempts: 3 + entrypoint: + - /bin/sh + environment: + - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://signoz-telemetrystore-clickhouse-0-0:9000 + - SIGNOZ_OTEL_COLLECTOR_TIMEOUT=10m + image: signoz/signoz-otel-collector:latest + networks: + - signoz-network +version: "3" +volumes: + signoz-metastore-postgres-0-data: null + signoz-telemetrykeeper-0-data: null + signoz-telemetrystore-0-0-data: null + signoz-telemetrystore-user-scripts: null diff --git a/docs/examples/dokploy/stack/pours/deployment/ingester/ingester.yaml b/docs/examples/dokploy/stack/pours/deployment/ingester/ingester.yaml new file mode 100644 index 00000000..44e476e6 --- /dev/null +++ b/docs/examples/dokploy/stack/pours/deployment/ingester/ingester.yaml @@ -0,0 +1,132 @@ +connectors: + signozmeter: + dimensions: + - name: service.name + - name: deployment.environment + - name: host.name + metrics_flush_interval: 1h +exporters: + clickhouselogsexporter: + dsn: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_logs + sending_queue: + enabled: false + timeout: 45s + use_new_schema: true + clickhousetraces: + datasource: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_traces + low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING} + sending_queue: + enabled: false + timeout: 45s + use_new_schema: true + metadataexporter: + cache: + provider: in_memory + dsn: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_metadata + enabled: true + timeout: 45s + signozclickhousemeter: + dsn: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_meter + sending_queue: + enabled: false + timeout: 45s + signozclickhousemetrics: + dsn: tcp://signoz-telemetrystore-clickhouse-0-0:9000/signoz_metrics + sending_queue: + enabled: false + timeout: 45s +extensions: + pprof: + endpoint: 0.0.0.0:1777 + signoz_health_check: + endpoint: 0.0.0.0:13133 +processors: + batch: + send_batch_max_size: 55000 + send_batch_size: 50000 + timeout: 5s + batch/meter: + send_batch_max_size: 25000 + send_batch_size: 20000 + timeout: 5s + signozspanmetrics/delta: + aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA + dimensions: + - default: default + name: service.namespace + - default: default + name: deployment.environment + - name: signoz.collector.id + - name: service.version + dimensions_cache_size: 100000 + enable_exp_histogram: true + latency_histogram_buckets: + - 100us + - 1ms + - 2ms + - 6ms + - 10ms + - 50ms + - 100ms + - 250ms + - 500ms + - 1000ms + - 1400ms + - 2000ms + - 5s + - 10s + - 20s + - 40s + - 60s + metrics_exporter: signozclickhousemetrics + metrics_flush_interval: 60s +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 +service: + extensions: + - signoz_health_check + - pprof + pipelines: + logs: + exporters: + - clickhouselogsexporter + - signozmeter + - metadataexporter + processors: + - batch + receivers: + - otlp + metrics: + exporters: + - signozclickhousemetrics + - signozmeter + - metadataexporter + processors: + - batch + receivers: + - otlp + metrics/meter: + exporters: + - signozclickhousemeter + processors: + - batch/meter + receivers: + - signozmeter + traces: + exporters: + - clickhousetraces + - signozmeter + - metadataexporter + processors: + - signozspanmetrics/delta + - batch + receivers: + - otlp + telemetry: + logs: + encoding: json diff --git a/docs/examples/dokploy/stack/pours/deployment/ingester/opamp.yaml b/docs/examples/dokploy/stack/pours/deployment/ingester/opamp.yaml new file mode 100644 index 00000000..2672ddb1 --- /dev/null +++ b/docs/examples/dokploy/stack/pours/deployment/ingester/opamp.yaml @@ -0,0 +1 @@ +server_endpoint: ws://signoz-signoz-0:4320/v1/opamp diff --git a/docs/examples/dokploy/stack/pours/deployment/telemetrykeeper/clickhousekeeper/keeper-0.yaml b/docs/examples/dokploy/stack/pours/deployment/telemetrykeeper/clickhousekeeper/keeper-0.yaml new file mode 100644 index 00000000..0434c1dc --- /dev/null +++ b/docs/examples/dokploy/stack/pours/deployment/telemetrykeeper/clickhousekeeper/keeper-0.yaml @@ -0,0 +1,22 @@ +keeper_server: + coordination_settings: + force_sync: false + operation_timeout_ms: 10000 + raft_logs_level: warning + session_timeout_ms: 30000 + snapshot_distance: 100000 + snapshots_to_keep: 3 + four_letter_word_white_list: '*' + log_storage_path: /var/lib/clickhouse-keeper/coordination/log + raft_configuration: + server: + - hostname: signoz-telemetrykeeper-clickhousekeeper-0 + id: 0 + port: 9234 + server_id: 0 + snapshot_storage_path: /var/lib/clickhouse-keeper/coordination/snapshots + tcp_port: 9181 +listen_host: 0.0.0.0 +logger: + console: true + level: information diff --git a/docs/examples/dokploy/stack/pours/deployment/telemetrystore/clickhouse/config-0-0.yaml b/docs/examples/dokploy/stack/pours/deployment/telemetrystore/clickhouse/config-0-0.yaml new file mode 100644 index 00000000..3e06179d --- /dev/null +++ b/docs/examples/dokploy/stack/pours/deployment/telemetrystore/clickhouse/config-0-0.yaml @@ -0,0 +1,96 @@ +asynchronous_metric_log: + ttl: event_date + INTERVAL 1 DAY DELETE +dictionaries_config: '*_dictionary.xml' +display_name: cluster +distributed_ddl: + path: /clickhouse/task_queue/ddl +error_log: + ttl: event_date + INTERVAL 1 DAY DELETE +format_schema_path: /var/lib/clickhouse/format_schemas/ +http_port: 8123 +interserver_http_port: 9009 +latency_log: + ttl: event_date + INTERVAL 1 DAY DELETE +listen_host: 0.0.0.0 +logger: + console: 1 + count: 10 + formatting: + type: console + level: information + size: 1000M +macros: + replica: "00" + shard: "00" +metric_log: + flush_interval_milliseconds: 30000 + ttl: event_date + INTERVAL 1 DAY DELETE +part_log: + ttl: event_date + INTERVAL 1 DAY DELETE +path: /var/lib/clickhouse/ +processors_profile_log: + flush_interval_milliseconds: 30000 + ttl: event_date + INTERVAL 1 DAY DELETE +profiles: + default: + allow_simdjson: 0 + load_balancing: random + log_queries: 1 +query_log: + flush_interval_milliseconds: 30000 + partition_by: toYYYYMM(event_date) + ttl: event_date + INTERVAL 1 DAY DELETE +query_metric_log: + ttl: event_date + INTERVAL 1 DAY DELETE +query_thread_log: + ttl: event_date + INTERVAL 1 DAY DELETE +query_views_log: + ttl: event_date + INTERVAL 1 DAY DELETE +quotas: + default: + interval: + duration: 3600 + errors: 0 + execution_time: 0 + queries: 0 + read_rows: 0 + result_rows: 0 +remote_servers: + cluster: + shard: + - replica: + - host: signoz-telemetrystore-clickhouse-0-0 + port: 9000 +session_log: + ttl: event_date + INTERVAL 1 DAY DELETE +tcp_port: 9000 +text_log: + flush_interval_milliseconds: 30000 + ttl: event_date + INTERVAL 1 DAY DELETE +tmp_path: /var/lib/clickhouse/tmp/ +trace_log: + flush_interval_milliseconds: 30000 + ttl: event_date + INTERVAL 1 DAY DELETE +user_defined_executable_functions_config: '*functions.yaml' +user_directories: + users_xml: + path: config-0-0.yaml +user_files_path: /var/lib/clickhouse/user_files/ +user_scripts_path: /var/lib/clickhouse/user_scripts/ +users: + default: + access_management: 1 + named_collection_control: 1 + networks: + ip: ::/0 + password: "" + profile: default + quota: default + show_named_collection: 1 + show_named_collection_secrets: 1 +zookeeper: + node: + - host: signoz-telemetrykeeper-clickhousekeeper-0 + port: 9181 +zookeeper_log: + ttl: event_date + INTERVAL 1 DAY DELETE diff --git a/docs/examples/dokploy/stack/pours/deployment/telemetrystore/clickhouse/functions.yaml b/docs/examples/dokploy/stack/pours/deployment/telemetrystore/clickhouse/functions.yaml new file mode 100644 index 00000000..475b4848 --- /dev/null +++ b/docs/examples/dokploy/stack/pours/deployment/telemetrystore/clickhouse/functions.yaml @@ -0,0 +1,13 @@ +functions: + argument: + - name: buckets + type: Array(Float64) + - name: counts + type: Array(Float64) + - name: quantile + type: Float64 + command: ./histogramQuantile + format: CSV + name: histogramQuantile + return_type: Float64 + type: executable diff --git a/internal/casting/dockerswarmcasting/casting.go b/internal/casting/dockerswarmcasting/casting.go index e9c8dc80..faa96cfc 100644 --- a/internal/casting/dockerswarmcasting/casting.go +++ b/internal/casting/dockerswarmcasting/casting.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/signoz/foundry/api/v1alpha1" "github.com/signoz/foundry/api/v1alpha1/installation" rootcasting "github.com/signoz/foundry/internal/casting" "github.com/signoz/foundry/internal/domain" @@ -33,6 +34,10 @@ func New(logger *slog.Logger) *dockerSwarmCasting { } } +func NewDokploy(logger *slog.Logger) *dockerSwarmCasting { + return New(logger) +} + func (casting *dockerSwarmCasting) Enricher(ctx context.Context, config *installation.Casting) (molding.MoldingEnricher, error) { return newDockerSwarmMoldingEnricher(config) } @@ -96,6 +101,13 @@ func (casting *dockerSwarmCasting) Forge(ctx context.Context, config installatio } func (casting *dockerSwarmCasting) Cast(ctx context.Context, config installation.Casting, outputPath string) error { + if config.Spec.Deployment.Platform == v1alpha1.PlatformDokploy { + casting.logger.InfoContext(ctx, "Deploy compose.yaml using Dokploy's stack feature", + slog.String("pours_path", outputPath), + slog.String("docs", "https://docs.dokploy.com/docs/core/docker-compose")) + return nil + } + casting.logger.InfoContext(ctx, "Deploying stack to Docker Swarm") composeFile := filepath.Join(outputPath, rootcasting.DeploymentDir, "compose.yaml") diff --git a/internal/casting/dockerswarmcasting/casting_test.go b/internal/casting/dockerswarmcasting/casting_test.go new file mode 100644 index 00000000..879c0d61 --- /dev/null +++ b/internal/casting/dockerswarmcasting/casting_test.go @@ -0,0 +1,58 @@ +package dockerswarmcasting + +import ( + "context" + "log/slog" + "path/filepath" + "strings" + "testing" + + "github.com/signoz/foundry/api/v1alpha1" + "github.com/signoz/foundry/api/v1alpha1/installation" + rootcasting "github.com/signoz/foundry/internal/casting" + "github.com/stretchr/testify/require" +) + +func TestDokployForgeProducesValidStackMaterials(t *testing.T) { + config := installation.Default(&installation.Casting{}) + config.Spec.Deployment.Platform = v1alpha1.PlatformDokploy + config.Spec.Deployment.Flavor = v1alpha1.FlavorStack + config.Spec.Ingester.Spec.Config.Data = map[string]string{ + "ingester.yaml": "service: {}", + "opamp.yaml": "server: {}", + } + + materials, err := NewDokploy(slog.Default()).Forge(context.Background(), *config, t.TempDir()) + require.NoError(t, err) + require.NotEmpty(t, materials) + + var compose []byte + paths := make(map[string]struct{}, len(materials)) + for _, material := range materials { + paths[material.Path()] = struct{}{} + if material.Path() == filepath.Join(rootcasting.DeploymentDir, "compose.yaml") { + compose = material.FmtContents() + } + } + + require.NotEmpty(t, compose) + require.NotContains(t, string(compose), "container_name") + require.NotContains(t, string(compose), "content:") + require.NotContains(t, string(compose), "exclude_from_hc") + require.Contains(t, string(compose), "name: dokploy-network") + require.Contains(t, string(compose), "external: true") + require.Contains(t, string(compose), "traefik.enable=true") + require.Contains(t, string(compose), "traefik.docker.network=dokploy-network") + require.Contains(t, string(compose), "traefik.http.routers.signoz-signoz.rule=Host(`${SIGNOZ_DOMAIN:?SIGNOZ_DOMAIN_required}`)") + require.Contains(t, string(compose), "traefik.http.services.signoz-signoz.loadbalancer.server.port=8080") + require.Equal(t, 1, strings.Count(string(compose), "- dokploy-network")) + require.Contains(t, paths, filepath.Join(rootcasting.DeploymentDir, "ingester", "ingester.yaml")) + require.Contains(t, paths, filepath.Join(rootcasting.DeploymentDir, "ingester", "opamp.yaml")) +} + +func TestDokployCastIsNoOp(t *testing.T) { + config := installation.Default(&installation.Casting{}) + config.Spec.Deployment.Platform = v1alpha1.PlatformDokploy + + require.NoError(t, NewDokploy(slog.Default()).Cast(context.Background(), *config, t.TempDir())) +} diff --git a/internal/casting/dockerswarmcasting/templates/compose.yaml.gotmpl b/internal/casting/dockerswarmcasting/templates/compose.yaml.gotmpl index 18781799..d4a39284 100644 --- a/internal/casting/dockerswarmcasting/templates/compose.yaml.gotmpl +++ b/internal/casting/dockerswarmcasting/templates/compose.yaml.gotmpl @@ -150,7 +150,11 @@ services: {{- end }} {{- end }} {{- $ingesterReplicas := int $.Spec.Ingester.Spec.Cluster.Replicas }} + {{- if eq $.Spec.Deployment.Platform.String "dokploy" }} + {{ $.Metadata.Name }}-ingester: + {{- else }} ingester: + {{- end }} image: {{ $.Spec.Ingester.Spec.Image }} networks: {{ $.Metadata.Name }}-network: @@ -168,9 +172,15 @@ services: target: /etc/otel-collector-config.yaml - source: manager-config target: /etc/opamp-config.yaml + {{- if eq $.Spec.Deployment.Platform.String "dokploy" }} + expose: + - "4317" + - "4318" + {{- else }} ports: - "4317:4317" - "4318:4318" + {{- end }} environment: {{ if and $.Spec.TelemetryStore.Spec.Enabled $.Spec.TelemetryStore.Status.Addresses.TCP (index $.Spec.TelemetryStore.Status.Addresses.TCP 0) }} - SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN={{ index $.Spec.TelemetryStore.Status.Addresses.TCP 0 }} @@ -187,19 +197,37 @@ services: image: {{ $.Spec.Signoz.Spec.Image }} networks: - {{ $.Metadata.Name }}-network + {{- if eq $.Spec.Deployment.Platform.String "dokploy" }} + - dokploy-network + {{- end }} {{- if $.Spec.Signoz.Spec.Env }} environment: {{- range $key, $value := $.Spec.Signoz.Spec.Env }} - {{ $key }}={{ $value }} {{- end }} {{- end }} + {{- if eq $.Spec.Deployment.Platform.String "dokploy" }} + expose: + - "8080" + {{- else }} ports: - "{{ add 8080 (mul $replicaIdx 1000) }}:8080" + {{- end }} {{- if eq $.Spec.MetaStore.Kind.String "sqlite" }} volumes: - {{ $.Metadata.Name }}-metastore-{{ $.Spec.MetaStore.Kind }}-{{ $replicaIdx }}-data:{{ dir (index $.Spec.Signoz.Spec.Env "SIGNOZ_SQLSTORE_SQLITE_PATH") }} {{- end }} deploy: + {{- if and (eq $.Spec.Deployment.Platform.String "dokploy") (eq $replicaIdx 0) }} + labels: + - traefik.enable=true + - traefik.docker.network=dokploy-network + - traefik.http.routers.{{ $.Metadata.Name }}-signoz.rule=Host(`${SIGNOZ_DOMAIN:?SIGNOZ_DOMAIN_required}`) + - traefik.http.routers.{{ $.Metadata.Name }}-signoz.entrypoints=websecure + - traefik.http.routers.{{ $.Metadata.Name }}-signoz.tls.certresolver=letsencrypt + - traefik.http.routers.{{ $.Metadata.Name }}-signoz.service={{ $.Metadata.Name }}-signoz + - traefik.http.services.{{ $.Metadata.Name }}-signoz.loadbalancer.server.port=8080 + {{- end }} restart_policy: condition: on-failure healthcheck: @@ -215,7 +243,11 @@ services: start_period: 60s {{- end }} {{- if derefBool $.Spec.MCP.Spec.Enabled }} + {{- if eq $.Spec.Deployment.Platform.String "dokploy" }} + {{ $.Metadata.Name }}-mcp: + {{- else }} mcp: + {{- end }} image: {{ $.Spec.MCP.Spec.Image }} networks: {{ $.Metadata.Name }}-network: @@ -227,8 +259,13 @@ services: - {{ $key }}={{ $value }} {{- end }} {{- end }} + {{- if eq $.Spec.Deployment.Platform.String "dokploy" }} + expose: + - "8000" + {{- else }} ports: - "8000:8000" + {{- end }} deploy: replicas: {{ int $.Spec.MCP.Spec.Cluster.Replicas }} restart_policy: @@ -266,6 +303,11 @@ networks: name: {{ $.Metadata.Name }}-network driver: overlay attachable: true + {{- if eq $.Spec.Deployment.Platform.String "dokploy" }} + dokploy-network: + name: dokploy-network + external: true + {{- end }} volumes: {{- range $replicaIdx := until (int $.Spec.TelemetryKeeper.Spec.Cluster.Replicas) }} {{ $.Metadata.Name }}-telemetrykeeper-{{ $replicaIdx }}-data: diff --git a/internal/casting/installation/registry.go b/internal/casting/installation/registry.go index bd6661f2..573a9b53 100644 --- a/internal/casting/installation/registry.go +++ b/internal/casting/installation/registry.go @@ -82,6 +82,12 @@ func NewRegistry(logger *slog.Logger) *Registry { }: { Casting: coolifycasting.New(logger), }, + { + Platform: v1alpha1.PlatformDokploy, + Flavor: v1alpha1.FlavorStack, + }: { + Casting: dockerswarmcasting.NewDokploy(logger), + }, { Platform: v1alpha1.PlatformRailway, Flavor: v1alpha1.FlavorTemplate,