Repository: jaegertracing/jaeger

Issue:
[Bug]: Jaeger v2 Using CRD OpenTelemetryCollector cannot use 0 replicas to create Elasticsarch Index
### What happened?

jaeger 2.6.0 use default replicas(1) to create elasticsearch index when CRD OpenTelemetryCollector use 0 replicas.

relates to
[#754 ](https://github.com/jaegertracing/jaeger/pull/754 )
Maybe this condition is affecting
https://github.com/jaegertracing/jaeger/blob/main/internal/storage/elasticsearch/config/config.go#L358

### Steps to reproduce

```yaml
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
  name: ${name}
  namespace: ${namespace}
spec:
  mode: deployment
  image: jaegertracing/jaeger:2.6.0
  ports:
  - name: jaeger
    port: 16686
  config:
    service:
      extensions: [jaeger_storage, jaeger_query]
      pipelines:
        traces:
          receivers: [otlp]    
          exporters: [jaeger_storage_exporter]
    extensions:
      jaeger_query:
        storage:
          traces: elasticsearch
      jaeger_storage:
        backends:
          elasticsearch:
            elasticsearch:
              server_urls:
                - http://localhost:9200
              indices:
                index_prefix: ""
                spans:
                  date_layout: "2006-01-02"
                  rollover_frequency: "day"
                  shards: 2
                  replicas: 0
                services:
                  date_layout: "2006-01-02"
                  rollover_frequency: "day"
                  shards: 2
                  replicas: 0
                dependencies:
                  date_layout: "2006-01-02"
                  rollover_frequency: "day"
                  shards: 2
                  replicas: 0
                sampling:
                  date_layout: "2006-01-02"
                  rollover_frequency: "day"
                  shards: 2
                  replicas: 0
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318
    exporters:
      jaeger_storage_exporter:
        trace_storage: elasticsearch

```

ES output settings
 "number_of_replicas":"1",

```json
{
   "jaeger-span-2025-05-14":{
      "settings":{
         "index":{
            "routing":{
               "allocation":{
                  "include":{
                     "_tier_preference":"data_content"
                  }
               }
            },
            "mapping":{
               "nested_fields":{
                  "limit":"50"
               }
            },
            "number_of_shards":"2",
            "provided_name":"jaeger-span-2025-05-14",
            "creation_date":"1747242100856",
            "requests":{
               "cache":{
                  "enable":"true"
               }
            },
            "number_of_replicas":"1",
            "uuid":"Z5eqFn_BTJObC8Hkssbcmw",
            "version":{
               "created":"8521000"
            }
         }
      }
   }
}
```

### Expected behavior

in ES index settings
```json
"number_of_replicas":"0"
```

### Relevant log output

```shell
elasticsearch index setting

{
   "jaeger-span-2025-05-14":{
      "settings":{
         "index":{
            "routing":{
               "allocation":{
                  "include":{
                     "_tier_preference":"data_content"
                  }
               }
            },
            "mapping":{
               "nested_fields":{
                  "limit":"50"
               }
            },
            "number_of_shards":"2",
            "provided_name":"jaeger-span-2025-05-14",
            "creation_date":"1747242100856",
            "requests":{
               "cache":{
                  "enable":"true"
               }
            },
            "number_of_replicas":"1",
            "uuid":"Z5eqFn_BTJObC8Hkssbcmw",
            "version":{
               "created":"8521000"
            }
         }
      }
   }
}
```

### Screenshot

_No response_

### Additional context

_No response_

### Jaeger backend version

v2.6.0

### SDK

_No response_

### Pipeline

OTLP > Jaeger v26.0 (using OpenTelemetryCollector CRD) > ES v8.17 storage

### Stogage backend

Elasticsearch 8.17

### Operating system

Linux

### Deployment model

Kubernetes with Opentelemetry-operator

### Deployment configs

```shell
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
  name: ${name}
  namespace: ${namespace}
spec:
  mode: deployment
  image: jaegertracing/jaeger:2.6.0
  ports:
  - name: jaeger
    port: 16686
  config:
    service:
      extensions: [jaeger_storage, jaeger_query]
      pipelines:
        traces:
          receivers: [otlp]    
          exporters: [jaeger_storage_exporter]
    extensions:
      jaeger_query:
        storage:
          traces: elasticsearch
      jaeger_storage:
        backends:
          elasticsearch:
            elasticsearch:
              server_urls:
                - http://localhost:9200
              indices:
                index_prefix: ""
                spans:
                  date_layout: "2006-01-02"
                  rollover_frequency: "day"
                  shards: 2
                  replicas: 0
                services:
                  date_layout: "2006-01-02"
                  rollover_frequency: "day"
                  shards: 2
                  replicas: 0
                dependencies:
                  date_layout: "2006-01-02"
                  rollover_frequency: "day"
                  shards: 2
                  replicas: 0
                sampling:
                  date_layout: "2006-01-02"
                  rollover_frequency: "day"
                  shards: 2
                  replicas: 0
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318
    exporters:
      jaeger_storage_exporter:
        trace_storage: elasticsearch
```

Discussion:
@yurishkuro: I think the only way we can support this today is by making this field a pointer and treating nil as unset (and subject to default) while 0 as valid value not to be overwritten by default.@Jaishree2310: Hi @yurishkuro,
I would like to take this token ID.
Thanks!
 @vignesh-codes: @yurishkuro I would like to raise a pr on this. Can you assign this issue to me?
@Jorgevillada: I tried 2.8.0 and the issue was resolved, closing the issue. ty

Solution patch:
diff --git a/cmd/es-rollover/app/init/flags.go b/cmd/es-rollover/app/init/flags.go
index 36c40aa8..5aca4e7e 100644
--- a/cmd/es-rollover/app/init/flags.go
+++ b/cmd/es-rollover/app/init/flags.go
@@ -28,6 +28,10 @@ type Config struct {
 	cfg.Indices
 }
 
+func ptr[T any](v T) *T {
+	return &v
+}
+
 // AddFlags adds flags for TLS to the FlagSet.
 func (*Config) AddFlags(flags *flag.FlagSet) {
 	flags.Int(shards, 5, "Number of shards")
@@ -45,10 +49,11 @@ func (c *Config) InitFromViper(v *viper.Viper) {
 	c.Indices.Dependencies.Shards = v.GetInt64(shards)
 	c.Indices.Sampling.Shards = v.GetInt64(shards)
 
-	c.Indices.Spans.Replicas = v.GetInt64(replicas)
-	c.Indices.Services.Replicas = v.GetInt64(replicas)
-	c.Indices.Dependencies.Replicas = v.GetInt64(replicas)
-	c.Indices.Sampling.Replicas = v.GetInt64(replicas)
+	replicas := v.GetInt64(replicas)
+	c.Indices.Spans.Replicas = ptr(replicas)
+	c.Indices.Services.Replicas = ptr(replicas)
+	c.Indices.Dependencies.Replicas = ptr(replicas)
+	c.Indices.Sampling.Replicas = ptr(replicas)
 
 	c.Indices.Spans.Priority = v.GetInt64(prioritySpanTemplate)
 	c.Indices.Services.Priority = v.GetInt64(priorityServiceTemplate)
diff --git a/internal/storage/elasticsearch/config/config.go b/internal/storage/elasticsearch/config/config.go
index a56211b6..36e16b67 100644
--- a/internal/storage/elasticsearch/config/config.go
+++ b/internal/storage/elasticsearch/config/config.go
@@ -49,7 +49,7 @@ type IndexOptions struct {
 	// Shards is the number of shards per index in Elasticsearch.
 	Shards int64 `mapstructure:"shards"`
 	// Replicas is the number of replicas per index in Elasticsearch.
-	Replicas int64 `mapstructure:"replicas"`
+	Replicas *int64 `mapstructure:"replicas"`
 	// RolloverFrequency contains the rollover frequency setting used to fetch
 	// indices from elasticsearch.
 	// Valid configuration options are: [hour, day].
@@ -355,7 +355,7 @@ func setDefaultIndexOptions(target, source *IndexOptions) {
 		target.Shards = source.Shards
 	}
 
-	if target.Replicas == 0 {
+	if target.Replicas == nil {
 		target.Replicas = source.Replicas
 	}
 
diff --git a/internal/storage/v1/elasticsearch/mappings/command.go b/internal/storage/v1/elasticsearch/mappings/command.go
index 0e81cdec..d36816be 100644
--- a/internal/storage/v1/elasticsearch/mappings/command.go
+++ b/internal/storage/v1/elasticsearch/mappings/command.go
@@ -52,7 +52,6 @@ func getMappingAsString(builder es.TemplateBuilder, opt Options) (string, error)
 	if err != nil {
 		return "", err
 	}
-
 	indexOpts := config.IndexOptions{
 		Shards:   opt.Shards,
 		Replicas: opt.Replicas,
diff --git a/internal/storage/v1/elasticsearch/mappings/flags.go b/internal/storage/v1/elasticsearch/mappings/flags.go
index 604c4306..86c569c1 100644
--- a/internal/storage/v1/elasticsearch/mappings/flags.go
+++ b/internal/storage/v1/elasticsearch/mappings/flags.go
@@ -12,7 +12,7 @@ type Options struct {
 	Mapping       string
 	EsVersion     uint
 	Shards        int64
-	Replicas      int64
+	Replicas      *int64
 	IndexPrefix   string
 	UseILM        string // using string as util is being used in python and using bool leads to type issues.
 	ILMPolicyName string
@@ -45,8 +45,10 @@ func (o *Options) AddFlags(command *cobra.Command) {
 		shardsFlag,
 		5,
 		"The number of shards per index in Elasticsearch")
+	// Allocate storage for Replicas so Int64Var can write into it.
+	o.Replicas = new(int64)
 	command.Flags().Int64Var(
-		&o.Replicas,
+		o.Replicas,
 		replicasFlag,
 		1,
 		"The number of replicas per index in Elasticsearch")
diff --git a/internal/storage/v1/elasticsearch/mappings/mapping.go b/internal/storage/v1/elasticsearch/mappings/mapping.go
index 1a0a3757..ed30e799 100644
--- a/internal/storage/v1/elasticsearch/mappings/mapping.go
+++ b/internal/storage/v1/elasticsearch/mappings/mapping.go
@@ -54,19 +54,19 @@ func (mb MappingBuilder) getMappingTemplateOptions(mappingType MappingType) temp
 	switch mappingType {
 	case SpanMapping:
 		mappingOpts.Shards = mb.Indices.Spans.Shards
-		mappingOpts.Replicas = mb.Indices.Spans.Replicas
+		mappingOpts.Replicas = *mb.Indices.Spans.Replicas
 		mappingOpts.Priority = mb.Indices.Spans.Priority
 	case ServiceMapping:
 		mappingOpts.Shards = mb.Indices.Services.Shards
-		mappingOpts.Replicas = mb.Indices.Services.Replicas
+		mappingOpts.Replicas = *mb.Indices.Services.Replicas
 		mappingOpts.Priority = mb.Indices.Services.Priority
 	case DependenciesMapping:
 		mappingOpts.Shards = mb.Indices.Dependencies.Shards
-		mappingOpts.Replicas = mb.Indices.Dependencies.Replicas
+		mappingOpts.Replicas = *mb.Indices.Dependencies.Replicas
 		mappingOpts.Priority = mb.Indices.Dependencies.Priority
 	case SamplingMapping:
 		mappingOpts.Shards = mb.Indices.Sampling.Shards
-		mappingOpts.Replicas = mb.Indices.Sampling.Replicas
+		mappingOpts.Replicas = *mb.Indices.Sampling.Replicas
 		mappingOpts.Priority = mb.Indices.Sampling.Priority
 	}
 
diff --git a/internal/storage/v1/elasticsearch/options.go b/internal/storage/v1/elasticsearch/options.go
index 47f84f6c..142071c0 100644
--- a/internal/storage/v1/elasticsearch/options.go
+++ b/internal/storage/v1/elasticsearch/options.go
@@ -75,7 +75,7 @@ var defaultIndexOptions = config.IndexOptions{
 	DateLayout:        initDateLayout(defaultIndexRolloverFrequency, defaultIndexDateSeparator),
 	RolloverFrequency: defaultIndexRolloverFrequency,
 	Shards:            5,
-	Replicas:          1,
+	Replicas:          ptr(int64(1)),
 	Priority:          0,
 }
 
@@ -114,6 +114,20 @@ func (cfg *namespaceConfig) getTLSFlagsConfig() tlscfg.ClientFlagsConfig {
 	}
 }
 
+// ptr returns a pointer to the given value.
+func ptr[T any](v T) *T {
+	return &v
+}
+
+// safeDerefInt64 safely dereferences a *int64 for use in flagSet.Int64.
+// If the pointer is nil (meaning no config was set), returns 0 as neutral default.
+func safeDerefInt64(ptr *int64) int64 {
+	if ptr != nil {
+		return *ptr
+	}
+	return 0
+}
+
 // AddFlags adds flags for Options
 func (opt *Options) AddFlags(flagSet *flag.FlagSet) {
 	addFlags(flagSet, &opt.Config)
@@ -164,7 +178,7 @@ func addFlags(flagSet *flag.FlagSet, nsConfig *namespaceConfig) {
 	)
 	flagSet.Int64(
 		nsConfig.namespace+suffixNumReplicas,
-		nsConfig.Indices.Spans.Replicas,
+		safeDerefInt64(nsConfig.Indices.Spans.Replicas),
 		"The number of replicas per index in Elasticsearch")
 	flagSet.Int64(
 		nsConfig.namespace+suffixPrioritySpanTemplate,
@@ -314,10 +328,13 @@ func initFromViper(cfg *namespaceConfig, v *viper.Viper) {
 	cfg.Indices.Sampling.Shards = v.GetInt64(cfg.namespace + suffixNumShards)
 	cfg.Indices.Dependencies.Shards = v.GetInt64(cfg.namespace + suffixNumShards)
 
-	cfg.Indices.Spans.Replicas = v.GetInt64(cfg.namespace + suffixNumReplicas)
-	cfg.Indices.Services.Replicas = v.GetInt64(cfg.namespace + suffixNumReplicas)
-	cfg.Indices.Sampling.Replicas = v.GetInt64(cfg.namespace + suffixNumReplicas)
-	cfg.Indices.Dependencies.Replicas = v.GetInt64(cfg.namespace + suffixNumReplicas)
+	// Note: We use a pointer type for Replicas to distinguish between "unset" and "explicit 0".
+	// Each field receives its own pointer to avoid accidental shared state.
+	replicas := v.GetInt64(cfg.namespace + suffixNumReplicas)
+	cfg.Indices.Spans.Replicas = ptr(replicas)
+	cfg.Indices.Services.Replicas = ptr(replicas)
+	cfg.Indices.Sampling.Replicas = ptr(replicas)
+	cfg.Indices.Dependencies.Replicas = ptr(replicas)
 
 	cfg.Indices.Spans.Priority = v.GetInt64(cfg.namespace + suffixPrioritySpanTemplate)
 	cfg.Indices.Services.Priority = v.GetInt64(cfg.namespace + suffixPriorityServiceTemplate)



Test patch:
diff --git a/cmd/es-rollover/app/init/action_test.go b/cmd/es-rollover/app/init/action_test.go
index afe53c25..4b3b9acf 100644
--- a/cmd/es-rollover/app/init/action_test.go
+++ b/cmd/es-rollover/app/init/action_test.go
@@ -16,6 +16,19 @@ import (
 	"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client/mocks"
 )
 
+func applyTestDefaults(cfg *Config) {
+	// Set defaults only if missing
+	if cfg.Indices.Spans.Shards == 0 {
+		cfg.Indices.Spans.Shards = 3
+	}
+	if cfg.Indices.Spans.Replicas == nil {
+		cfg.Indices.Spans.Replicas = ptr(int64(1))
+	}
+	if cfg.Indices.Spans.Priority == 0 {
+		cfg.Indices.Spans.Priority = 10
+	}
+}
+
 func TestIndexCreateIfNotExist(t *testing.T) {
 	tests := []struct {
 		name           string
@@ -235,6 +248,8 @@ func TestRolloverAction(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
+			// Apply local test defaults
+			applyTestDefaults(&test.config)
 			indexClient := &mocks.IndexAPI{}
 			clusterClient := &mocks.ClusterAPI{}
 			ilmClient := &mocks.IndexManagementLifecycleAPI{}
diff --git a/cmd/es-rollover/app/init/flags_test.go b/cmd/es-rollover/app/init/flags_test.go
index 3409cffd..4d76c42d 100644
--- a/cmd/es-rollover/app/init/flags_test.go
+++ b/cmd/es-rollover/app/init/flags_test.go
@@ -34,7 +34,8 @@ func TestBindFlags(t *testing.T) {
 
 	c.InitFromViper(v)
 	assert.EqualValues(t, 8, c.Indices.Spans.Shards)
-	assert.EqualValues(t, 16, c.Indices.Spans.Replicas)
+	require.NotNil(t, c.Indices.Spans.Replicas)
+	assert.EqualValues(t, 16, *c.Indices.Spans.Replicas)
 	assert.EqualValues(t, 300, c.Indices.Spans.Priority)
 	assert.EqualValues(t, 301, c.Indices.Services.Priority)
 	assert.EqualValues(t, 302, c.Indices.Dependencies.Priority)
diff --git a/internal/storage/elasticsearch/config/config_test.go b/internal/storage/elasticsearch/config/config_test.go
index f6df53c4..72af30e6 100644
--- a/internal/storage/elasticsearch/config/config_test.go
+++ b/internal/storage/elasticsearch/config/config_test.go
@@ -75,6 +75,10 @@ func copyToTempFile(t *testing.T, pattern string, filename string) (file *os.Fil
 	return tempFile
 }
 
+func int64Ptr(v int64) *int64 {
+	return &v
+}
+
 func TestNewClient(t *testing.T) {
 	const (
 		pwd1       = "password"
@@ -445,17 +449,17 @@ func TestApplyDefaults(t *testing.T) {
 			IndexPrefix: "hello",
 			Spans: IndexOptions{
 				Shards:   5,
-				Replicas: 1,
+				Replicas: int64Ptr(1),
 				Priority: 10,
 			},
 			Services: IndexOptions{
 				Shards:   5,
-				Replicas: 1,
+				Replicas: int64Ptr(1),
 				Priority: 20,
 			},
 			Dependencies: IndexOptions{
 				Shards:   5,
-				Replicas: 1,
+				Replicas: int64Ptr(1),
 				Priority: 30,
 			},
 			Sampling: IndexOptions{},
@@ -528,17 +532,17 @@ func TestApplyDefaults(t *testing.T) {
 					IndexPrefix: "hello",
 					Spans: IndexOptions{
 						Shards:   5,
-						Replicas: 1,
+						Replicas: int64Ptr(1),
 						Priority: 10,
 					},
 					Services: IndexOptions{
 						Shards:   5,
-						Replicas: 1,
+						Replicas: int64Ptr(1),
 						Priority: 20,
 					},
 					Dependencies: IndexOptions{
 						Shards:   5,
-						Replicas: 1,
+						Replicas: int64Ptr(1),
 						Priority: 30,
 					},
 				},
@@ -574,17 +578,17 @@ func TestApplyDefaults(t *testing.T) {
 					IndexPrefix: "hello",
 					Spans: IndexOptions{
 						Shards:   5,
-						Replicas: 1,
+						Replicas: int64Ptr(1),
 						Priority: 10,
 					},
 					Services: IndexOptions{
 						Shards:   5,
-						Replicas: 1,
+						Replicas: int64Ptr(1),
 						Priority: 20,
 					},
 					Dependencies: IndexOptions{
 						Shards:   5,
-						Replicas: 1,
+						Replicas: int64Ptr(1),
 						Priority: 30,
 					},
 				},
diff --git a/internal/storage/v1/elasticsearch/factory_test.go b/internal/storage/v1/elasticsearch/factory_test.go
index ac44ed36..b032a04d 100644
--- a/internal/storage/v1/elasticsearch/factory_test.go
+++ b/internal/storage/v1/elasticsearch/factory_test.go
@@ -214,7 +214,19 @@ func TestCreateTemplates(t *testing.T) {
 		}
 		f.logger = zaptest.NewLogger(t)
 		f.metricsFactory = metrics.NullFactory
-		f.config = &escfg.Configuration{CreateIndexTemplates: true, Indices: escfg.Indices{IndexPrefix: test.indexPrefix}}
+		f.config = &escfg.Configuration{CreateIndexTemplates: true, Indices: escfg.Indices{
+			IndexPrefix: test.indexPrefix,
+			Spans: escfg.IndexOptions{
+				Shards:   3,
+				Replicas: ptr(int64(1)),
+				Priority: 10,
+			},
+			Services: escfg.IndexOptions{
+				Shards:   3,
+				Replicas: ptr(int64(1)),
+				Priority: 10,
+			},
+		}}
 		f.tracer = otel.GetTracerProvider()
 		client, err := f.newClientFn(&escfg.Configuration{}, zaptest.NewLogger(t), metrics.NullFactory)
 		require.NoError(t, err)
@@ -323,10 +335,10 @@ func testPasswordFromFile(t *testing.T) {
 	})
 
 	writer := spanstore.NewSpanWriter(f.GetSpanWriterParams())
-	span := &dbmodel.Span{
+	span1 := &dbmodel.Span{
 		Process: dbmodel.Process{ServiceName: "foo"},
 	}
-	writer.WriteSpan(time.Now(), span)
+	writer.WriteSpan(time.Now(), span1)
 	assert.Eventually(t,
 		func() bool {
 			pwd, ok := authReceived.Load(upwd1)
@@ -351,7 +363,10 @@ func testPasswordFromFile(t *testing.T) {
 		"expecting es.Client to change for the new password",
 	)
 
-	writer.WriteSpan(time.Now(), span)
+	span2 := &dbmodel.Span{
+		Process: dbmodel.Process{ServiceName: "foo"},
+	}
+	writer.WriteSpan(time.Now(), span2)
 	assert.Eventually(t,
 		func() bool {
 			pwd, ok := authReceived.Load(upwd2)
diff --git a/internal/storage/v1/elasticsearch/mappings/command_test.go b/internal/storage/v1/elasticsearch/mappings/command_test.go
index 970e6073..c232ec64 100644
--- a/internal/storage/v1/elasticsearch/mappings/command_test.go
+++ b/internal/storage/v1/elasticsearch/mappings/command_test.go
@@ -85,19 +85,19 @@ func Test_getMappingAsString(t *testing.T) {
 		wantErr error
 	}{
 		{
-			name: "ES version 7", args: Options{Mapping: "jaeger-span", EsVersion: 7, Shards: 5, Replicas: 1, IndexPrefix: "test", UseILM: "true", ILMPolicyName: "jaeger-test-policy"},
+			name: "ES version 7", args: Options{Mapping: "jaeger-span", EsVersion: 7, Shards: 5, Replicas: ptr(int64(1)), IndexPrefix: "test", UseILM: "true", ILMPolicyName: "jaeger-test-policy"},
 			want: "ES version 7",
 		},
 		{
-			name: "Parse Error version 7", args: Options{Mapping: "jaeger-span", EsVersion: 7, Shards: 5, Replicas: 1, IndexPrefix: "test", UseILM: "true", ILMPolicyName: "jaeger-test-policy"},
+			name: "Parse Error version 7", args: Options{Mapping: "jaeger-span", EsVersion: 7, Shards: 5, Replicas: ptr(int64(1)), IndexPrefix: "test", UseILM: "true", ILMPolicyName: "jaeger-test-policy"},
 			wantErr: errors.New("parse error"),
 		},
 		{
-			name: "Parse bool error", args: Options{Mapping: "jaeger-span", EsVersion: 7, Shards: 5, Replicas: 1, IndexPrefix: "test", UseILM: "foo", ILMPolicyName: "jaeger-test-policy"},
+			name: "Parse bool error", args: Options{Mapping: "jaeger-span", EsVersion: 7, Shards: 5, Replicas: ptr(int64(1)), IndexPrefix: "test", UseILM: "foo", ILMPolicyName: "jaeger-test-policy"},
 			wantErr: errors.New("strconv.ParseBool: parsing \"foo\": invalid syntax"),
 		},
 		{
-			name: "Invalid Mapping type", args: Options{Mapping: "invalid-mapping", EsVersion: 7, Shards: 5, Replicas: 1, IndexPrefix: "test", UseILM: "true", ILMPolicyName: "jaeger-test-policy"},
+			name: "Invalid Mapping type", args: Options{Mapping: "invalid-mapping", EsVersion: 7, Shards: 5, Replicas: ptr(int64(1)), IndexPrefix: "test", UseILM: "true", ILMPolicyName: "jaeger-test-policy"},
 			wantErr: errors.New("invalid mapping type: invalid-mapping"),
 		},
 	}
@@ -148,7 +148,7 @@ func TestGenerateMappings(t *testing.T) {
 				Mapping:       "jaeger-span",
 				EsVersion:     7,
 				Shards:        5,
-				Replicas:      1,
+				Replicas:      ptr(int64(1)),
 				IndexPrefix:   "jaeger-index",
 				UseILM:        "false",
 				ILMPolicyName: "jaeger-ilm-policy",
@@ -161,7 +161,7 @@ func TestGenerateMappings(t *testing.T) {
 				Mapping:       "jaeger-service",
 				EsVersion:     7,
 				Shards:        5,
-				Replicas:      1,
+				Replicas:      ptr(int64(1)),
 				IndexPrefix:   "jaeger-service-index",
 				UseILM:        "true",
 				ILMPolicyName: "service-ilm-policy",
diff --git a/internal/storage/v1/elasticsearch/mappings/flags_test.go b/internal/storage/v1/elasticsearch/mappings/flags_test.go
index ebc957d3..32b2dd4d 100644
--- a/internal/storage/v1/elasticsearch/mappings/flags_test.go
+++ b/internal/storage/v1/elasticsearch/mappings/flags_test.go
@@ -19,7 +19,7 @@ func TestOptionsWithDefaultFlags(t *testing.T) {
 	assert.Empty(t, o.Mapping)
 	assert.Equal(t, uint(7), o.EsVersion)
 	assert.EqualValues(t, 5, o.Shards)
-	assert.EqualValues(t, 1, o.Replicas)
+	assert.EqualValues(t, 1, *o.Replicas)
 
 	assert.Empty(t, o.IndexPrefix)
 	assert.Equal(t, "false", o.UseILM)
@@ -44,7 +44,7 @@ func TestOptionsWithFlags(t *testing.T) {
 	assert.Equal(t, "jaeger-span", o.Mapping)
 	assert.Equal(t, uint(7), o.EsVersion)
 	assert.Equal(t, int64(5), o.Shards)
-	assert.Equal(t, int64(1), o.Replicas)
+	assert.Equal(t, int64(1), *o.Replicas)
 	assert.Equal(t, "test", o.IndexPrefix)
 	assert.Equal(t, "true", o.UseILM)
 	assert.Equal(t, "jaeger-test-policy", o.ILMPolicyName)
diff --git a/internal/storage/v1/elasticsearch/mappings/mapping_test.go b/internal/storage/v1/elasticsearch/mappings/mapping_test.go
index daa9f6e4..fbb90aa5 100644
--- a/internal/storage/v1/elasticsearch/mappings/mapping_test.go
+++ b/internal/storage/v1/elasticsearch/mappings/mapping_test.go
@@ -25,6 +25,10 @@ import (
 //go:embed fixtures/*.json
 var FIXTURES embed.FS
 
+func ptr[T any](v T) *T {
+	return &v
+}
+
 func TestMappingBuilderGetMapping(t *testing.T) {
 	tests := []struct {
 		mapping   MappingType
@@ -47,7 +51,7 @@ func TestMappingBuilderGetMapping(t *testing.T) {
 			defaultOpts := func(p int64) config.IndexOptions {
 				return config.IndexOptions{
 					Shards:   3,
-					Replicas: 3,
+					Replicas: ptr(int64(3)),
 					Priority: p,
 				}
 			}
@@ -176,7 +180,7 @@ func TestMappingBuilderFixMapping(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 			indexTemOps := config.IndexOptions{
 				Shards:   3,
-				Replicas: 5,
+				Replicas: ptr(int64(5)),
 				Priority: 500,
 			}
 			mappingBuilder := MappingBuilder{
@@ -324,7 +328,7 @@ func TestMappingBuilderGetSpanServiceMappings(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 			indexTemOps := config.IndexOptions{
 				Shards:   3,
-				Replicas: 3,
+				Replicas: ptr(int64(3)),
 			}
 
 			mappingBuilder := MappingBuilder{
@@ -357,6 +361,13 @@ func TestMappingBuilderGetDependenciesMappings(t *testing.T) {
 
 	mappingBuilder := MappingBuilder{
 		TemplateBuilder: &tb,
+		Indices: config.Indices{
+			Dependencies: config.IndexOptions{
+				Replicas: ptr(int64(1)),
+				Shards:   3,
+				Priority: 10,
+			},
+		},
 	}
 	_, err := mappingBuilder.GetDependenciesMappings()
 	require.EqualError(t, err, "template load error")
@@ -370,6 +381,13 @@ func TestMappingBuilderGetSamplingMappings(t *testing.T) {
 
 	mappingBuilder := MappingBuilder{
 		TemplateBuilder: &tb,
+		Indices: config.Indices{
+			Sampling: config.IndexOptions{
+				Replicas: ptr(int64(1)),
+				Shards:   3,
+				Priority: 10,
+			},
+		},
 	}
 	_, err := mappingBuilder.GetSamplingMappings()
 	require.EqualError(t, err, "template load error")
diff --git a/internal/storage/v1/elasticsearch/options_test.go b/internal/storage/v1/elasticsearch/options_test.go
index ece7ab45..e2753c67 100644
--- a/internal/storage/v1/elasticsearch/options_test.go
+++ b/internal/storage/v1/elasticsearch/options_test.go
@@ -27,10 +27,14 @@ func TestOptions(t *testing.T) {
 	assert.EqualValues(t, 5, primary.Indices.Services.Shards)
 	assert.EqualValues(t, 5, primary.Indices.Sampling.Shards)
 	assert.EqualValues(t, 5, primary.Indices.Dependencies.Shards)
-	assert.EqualValues(t, 1, primary.Indices.Spans.Replicas)
-	assert.EqualValues(t, 1, primary.Indices.Services.Replicas)
-	assert.EqualValues(t, 1, primary.Indices.Sampling.Replicas)
-	assert.EqualValues(t, 1, primary.Indices.Dependencies.Replicas)
+	require.NotNil(t, primary.Indices.Spans.Replicas)
+	assert.EqualValues(t, 1, *primary.Indices.Spans.Replicas)
+	require.NotNil(t, primary.Indices.Services.Replicas)
+	assert.EqualValues(t, 1, *primary.Indices.Services.Replicas)
+	require.NotNil(t, primary.Indices.Sampling.Replicas)
+	assert.EqualValues(t, 1, *primary.Indices.Sampling.Replicas)
+	require.NotNil(t, primary.Indices.Dependencies.Replicas)
+	assert.EqualValues(t, 1, *primary.Indices.Dependencies.Replicas)
 	assert.Equal(t, 72*time.Hour, primary.MaxSpanAge)
 	assert.False(t, primary.Sniffing.Enabled)
 	assert.False(t, primary.Sniffing.UseHTTPS)