diff --git a/pkg/dnservice/store.go b/pkg/dnservice/store.go
index 5565ca19019768bff03469b7f1d112202c1ec9bb..b810dec73dc8618d7ae1b7903af7346493b1f330 100644
--- a/pkg/dnservice/store.go
+++ b/pkg/dnservice/store.go
@@ -394,7 +394,10 @@ func (s *store) initFileService() error {
 		return err
 	}
 
-	s.fs = fileservice.NewFileServices(localFileServiceName, localFS, s3FS)
+	s.fs, err = fileservice.NewFileServices(localFileServiceName, localFS, s3FS)
+	if err != nil {
+		return err
+	}
 	s.metadataFS = rfs
 
 	return nil
diff --git a/pkg/fileservice/errors.go b/pkg/fileservice/errors.go
index 1e709e831ddfe6bc599914c4b21b1d21ba1407c2..eb3581bdcfb9d04a79b7460a4269940b6c336d74 100644
--- a/pkg/fileservice/errors.go
+++ b/pkg/fileservice/errors.go
@@ -20,10 +20,11 @@ import (
 )
 
 var (
-	ErrFileNotFound  = errors.New("file not found")
-	ErrFileExisted   = errors.New("file existed")
-	ErrUnexpectedEOF = io.ErrUnexpectedEOF
-	ErrSizeNotMatch  = errors.New("size not match")
-	ErrEmptyRange    = errors.New("empty range")
-	ErrEmptyVector   = errors.New("empty vector")
+	ErrFileNotFound   = errors.New("file not found")
+	ErrFileExisted    = errors.New("file existed")
+	ErrUnexpectedEOF  = io.ErrUnexpectedEOF
+	ErrSizeNotMatch   = errors.New("size not match")
+	ErrEmptyRange     = errors.New("empty range")
+	ErrEmptyVector    = errors.New("empty vector")
+	ErrDuplicatedName = errors.New("duplicated name")
 )
diff --git a/pkg/fileservice/file_service.go b/pkg/fileservice/file_service.go
index 0f5b55efdb13b9ced21e2fe68d60a9da26194476..bed8ad113fca4691524ed883e2b0ff2147aa7a91 100644
--- a/pkg/fileservice/file_service.go
+++ b/pkg/fileservice/file_service.go
@@ -22,6 +22,7 @@ import (
 // FileService is a write-once file system
 type FileService interface {
 	// Name is file service's name
+	// service name is case-insensitive
 	Name() string
 
 	// Write writes a new file
@@ -46,12 +47,16 @@ type FileService interface {
 }
 
 type IOVector struct {
-	// path to file, '/' separated
-	// add a file service name prefix to select different service
-	// for example, 's3:a/b/c' refer to the path 'a/b/c' in S3
+	// FilePath indicates where to find the file
+	// a path has two parts, service name and file name, separated by ':'
+	// service name is optional, if omitted, the receiver FileService will use the default name of the service
+	// file name parts are separated by '/'
+	// valid characters in file names are intersection of valid characters of S3 and native file systems
+	// example:
+	// s3:a/b/c S3:a/b/c represents the same file 'a/b/c' located in 'S3' service
 	FilePath string
 	// io entries
-	// empty entry not allowed
+	// empty Entries not allowed
 	Entries []IOEntry
 }
 
diff --git a/pkg/fileservice/file_service_test.go b/pkg/fileservice/file_service_test.go
index fca5712f620f2364e9a656934b0d2d0b28e067c3..c440d3eb2be42337d2d8a48a5eac25b4ed53c906 100644
--- a/pkg/fileservice/file_service_test.go
+++ b/pkg/fileservice/file_service_test.go
@@ -448,7 +448,7 @@ func testFileService(
 		fs := newFS()
 
 		err := fs.Write(ctx, IOVector{
-			FilePath: fs.Name() + ":foo",
+			FilePath: joinPath(fs.Name(), "foo"),
 			Entries: []IOEntry{
 				{
 					Size: 4,
@@ -470,6 +470,30 @@ func testFileService(
 		assert.Nil(t, err)
 		assert.Equal(t, []byte("1234"), vec.Entries[0].Data)
 
+		vec = IOVector{
+			FilePath: joinPath(strings.ToLower(fs.Name()), "foo"),
+			Entries: []IOEntry{
+				{
+					Size: -1,
+				},
+			},
+		}
+		err = fs.Read(ctx, &vec)
+		assert.Nil(t, err)
+		assert.Equal(t, []byte("1234"), vec.Entries[0].Data)
+
+		vec = IOVector{
+			FilePath: joinPath(strings.ToUpper(fs.Name()), "foo"),
+			Entries: []IOEntry{
+				{
+					Size: -1,
+				},
+			},
+		}
+		err = fs.Read(ctx, &vec)
+		assert.Nil(t, err)
+		assert.Equal(t, []byte("1234"), vec.Entries[0].Data)
+
 	})
 
 }
diff --git a/pkg/fileservice/file_services.go b/pkg/fileservice/file_services.go
index e8fd6515777c3301a8d4551735ad74abc23b4bc4..3fc7de2f275c307aba5176be9c61a67fe2dccace 100644
--- a/pkg/fileservice/file_services.go
+++ b/pkg/fileservice/file_services.go
@@ -17,6 +17,7 @@ package fileservice
 import (
 	"context"
 	"fmt"
+	"strings"
 )
 
 type FileServices struct {
@@ -24,19 +25,19 @@ type FileServices struct {
 	mappings    map[string]FileService
 }
 
-func NewFileServices(defaultName string, fss ...FileService) *FileServices {
+func NewFileServices(defaultName string, fss ...FileService) (*FileServices, error) {
 	f := &FileServices{
 		defaultName: defaultName,
 		mappings:    make(map[string]FileService),
 	}
 	for _, fs := range fss {
-		name := fs.Name()
+		name := strings.ToLower(fs.Name())
 		if _, ok := f.mappings[name]; ok {
-			panic(fmt.Errorf("duplicated file service name: %s", name))
+			return nil, fmt.Errorf("%w: %s", ErrDuplicatedName, name)
 		}
 		f.mappings[name] = fs
 	}
-	return f
+	return f, nil
 }
 
 var _ FileService = &FileServices{}
diff --git a/pkg/fileservice/file_services_test.go b/pkg/fileservice/file_services_test.go
index 0bbfea8568be5b4223283ea4efc6e988559161b5..367e4291e925cd50313b9793bb95253f1ae48270 100644
--- a/pkg/fileservice/file_services_test.go
+++ b/pkg/fileservice/file_services_test.go
@@ -26,7 +26,18 @@ func TestFileServices(t *testing.T) {
 			dir := t.TempDir()
 			fs, err := NewLocalFS("local", dir, 0)
 			assert.Nil(t, err)
-			return NewFileServices("local", fs)
+			fs2, err := NewFileServices("local", fs)
+			assert.Nil(t, err)
+			return fs2
 		})
 	})
 }
+
+func TestFileServicesNameCaseInsensitive(t *testing.T) {
+	fs1, err := NewMemoryFS("foo")
+	assert.Nil(t, err)
+	fs2, err := NewMemoryFS("FOO")
+	assert.Nil(t, err)
+	_, err = NewFileServices(fs1.Name(), fs1, fs2)
+	assert.ErrorIs(t, err, ErrDuplicatedName)
+}
diff --git a/pkg/fileservice/get.go b/pkg/fileservice/get.go
index b337f7423fd990d92b5e9f13df90f1c36257802a..5899c1d5e27da8347587e3444692df32b0150f4e 100644
--- a/pkg/fileservice/get.go
+++ b/pkg/fileservice/get.go
@@ -14,11 +14,15 @@
 
 package fileservice
 
-import "fmt"
+import (
+	"fmt"
+	"strings"
+)
 
 func Get[T any](fs FileService, name string) (res T, err error) {
+	lowerName := strings.ToLower(name)
 	if fs, ok := fs.(*FileServices); ok {
-		f, ok := fs.mappings[name]
+		f, ok := fs.mappings[lowerName]
 		if !ok {
 			err = fmt.Errorf("file service not found: %s", name)
 			return
@@ -36,7 +40,7 @@ func Get[T any](fs FileService, name string) (res T, err error) {
 		err = fmt.Errorf("%T does not implement %T", fs, res)
 		return
 	}
-	if fs.Name() != name {
+	if !strings.EqualFold(fs.Name(), lowerName) {
 		err = fmt.Errorf("file service name not match, expecting %s, got %s", name, fs.Name())
 		return
 	}
diff --git a/pkg/fileservice/path.go b/pkg/fileservice/path.go
index fb034115352d56f04a507e4fd833c0d96e9cd304..251289b8e34c3c0309e828e711191dfdfe8195e6 100644
--- a/pkg/fileservice/path.go
+++ b/pkg/fileservice/path.go
@@ -19,13 +19,25 @@ import (
 	"strings"
 )
 
+const ServiceNameSeparator = ":"
+
 func splitPath(serviceName string, path string) (string, string, error) {
-	parts := strings.SplitN(path, ":", 2)
+	parts := strings.SplitN(path, ServiceNameSeparator, 2)
 	if len(parts) == 2 {
-		if serviceName != "" && parts[0] != "" && parts[0] != serviceName {
+		if serviceName != "" &&
+			parts[0] != "" &&
+			!strings.EqualFold(parts[0], serviceName) {
 			return "", "", fmt.Errorf("wrong file service name, expecting %s, got %s", serviceName, parts[0])
 		}
 		return parts[0], parts[1], nil
 	}
 	return "", parts[0], nil
 }
+
+func joinPath(serviceName string, path string) string {
+	buf := new(strings.Builder)
+	buf.WriteString(serviceName)
+	buf.WriteString(ServiceNameSeparator)
+	buf.WriteString(path)
+	return buf.String()
+}