Repository: erpc/erpc
Pull request #244: fix: update Docker image to use distroless image and non-root user
Author: login: devin-ai-integration[bot]
Created: 2025-04-27T16:46:18Z | Merged: 2025-04-28T10:43:05Z

# Update Docker image to improve security and reduce size

This PR updates the Docker image for the erpc project to improve security and reduce its size by:

1. Using `gcr.io/distroless/static-debian12:nonroot` as the base image instead of `debian:stable`
2. Eliminating the requirement for a root user by running as the nonroot user (UID 65532)
3. Removing unnecessary CA certificates installation since distroless images don't support apt-get
4. Adjusting the COPY instructions and paths to work with the nonroot user
5. Adding port 4000 to the EXPOSE directive to match the default HTTP port used by the application

## Testing
- Built the Docker image successfully
- Verified the container runs with the nonroot user (UID 65532)
- Confirmed the service is accessible on port 4000 and returns a successful health check response

## Related Issue
Fixes #238

## Link to Devin run
https://app.devin.ai/sessions/89e45214c3464063b4c44498cbd3c0ea

## Requested by
Aram Alipoor (aram@erpc.cloud)


Linked issue #238: bug: container runs as root user
**Describe the bug**
Currently erpc runs as a root user by default, this causes failures when attempting to start a pod with security context specifying to run as non root.

```yaml
securityContext:
  runAsNonRoot: true
```

```yaml
    state:
      waiting:
        message: 'container has runAsNonRoot and image will run as root (pod: "<REDACTED>",
          container: erpc)'
        reason: CreateContainerConfigError
```

The same can be determined outside of Kubernetes via:
```bash
docker run --rm ghcr.io/erpc/erpc:0.0.44 sh -c 'if [ "$(id -u)" -eq 0 ]; then echo "<0001f7e5> Running as root"; else echo "<0001f7e9> Not running as root"; fi' 
🟥 Running as root
```

Simply specifying a non-root user to run as, doesn't solve the problem either in Kubernetes
```yaml
securityContext:
  runAsUser: 1000
```

```yaml
    lastState:
      terminated:
        containerID: containerd://a1e437b361a0dfcf0f50794b918999b945c8c58949a2c8d48bc2a84f655f9a93
        exitCode: 128
        finishedAt: "2025-04-24T10:09:28Z"
        message: 'failed to create containerd task: failed to create shim task: OCI
          runtime create failed: runc create failed: unable to start container process:
          exec: "/root/erpc-server": stat /root/erpc-server: permission denied: unknown'
        reason: StartError
        startedAt: "1970-01-01T00:00:00Z"
```

Unfortunately it doesn't seem possible to reproduce this with docker `--user` option, which appears to be more lenient with permissions here and will allow the application to start unlike containerd.
```
docker run -it --user 1000:1000 --rm  ghcr.io/erpc/erpc:0.0.44 
```

**To Reproduce**
Steps to reproduce the behavior:
1. Use erpc version (e.g `0.0.44`)
2. Use the following kubernetes security context:
```yaml
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
```

**Expected behavior**
Application should start

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Relevant Logs:**
N/A

**Additional context**
Add any other context about the problem here.

Stripped k8s manifest we're running with may be found below.
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/instance: erpc
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: erpc
    app.kubernetes.io/version: 0.0.43
    helm.sh/chart: erpc-0.1.0
  name: erpc
  namespace: blockchain-proxy
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app.kubernetes.io/instance: erpc
      app.kubernetes.io/name: erpc
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 20%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app.kubernetes.io/instance: erpc
        app.kubernetes.io/name: erpc
    spec:
      containers:
      - envFrom:
        - secretRef:
            name: erpc-secret
        image: ghcr.io/erpc/erpc:0.0.43
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /healthcheck
            port: http
            scheme: HTTP
          initialDelaySeconds: 45
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        name: erpc
        ports:
        - containerPort: 4000
          name: http
          protocol: TCP
        - containerPort: 9090
          name: metrics
          protocol: TCP
        readinessProbe:
          failureThreshold: 1
          httpGet:
            path: /healthcheck
            port: http
            scheme: HTTP
          initialDelaySeconds: 45
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          limits:
            cpu: "1"
            memory: 512Mi
          requests:
            cpu: 500m
            memory: 256Mi
        securityContext:
          capabilities:
            drop:
            - ALL
          readOnlyRootFilesystem: true
          runAsGroup: 1000
          runAsNonRoot: true
          runAsUser: 1000
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /root/erpc.yaml
          name: config
          subPath: erpc.yaml
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext:
        fsGroup: 2000
      terminationGracePeriodSeconds: 30
      volumes:
      - configMap:
          defaultMode: 420
          name: erpc
        name: config
```
Linked issue comments: user: login: wparr-circle
created_at: 2025-04-24T11:05:59Z
body: Avoiding putting a pull up for now, as I need to check the CLA.

But in general my suggestion would be to utilise a distroless non-root image as the final build stage `FROM`.
These tend to have the following users baked in:
```
root:x:0:0:root:/root:/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/sbin/nologin
nonroot:x:65532:65532:nonroot:/home/nonroot:/sbin/nologin
```

Tested a small change which is currently in my fork earlier today: https://github.com/wparr-circle/erpc/commit/6dfb367fee08f8217821bd2e0191f4835534b032 (although tested locally with `gcr.io/distroless/static-debian12:debug-nonroot`) where `erpc:non-root` is a locally built image.

```bash
docker run --entrypoint=sh --rm erpc:non-root -c 'if [ "$(id -u)" -eq 0 ]; then echo "<0001f7e5> Running as root"; else echo "<0001f7e9> Not running as root"; fi' 
🟩 Not running as root
```

This should work for the security context options within Kubernetes, although I'm yet to get a successful validation given my machine is running a cluster with linux/arm64 and the docker image only builds for [amd64](https://github.com/erpc/erpc/blob/main/Dockerfile#L30).
Supporting multi-arch here might be useful as well 🤔 

This also has an added benefit of greatly reducing the final image size as well as reducing attack surface.
ie. ghcr.io/erpc/erpc:0.0.44 is 239.76 MB, whereas distroless based one is 131.08 MB

---

Managed to get some multi-arch support added in to build with arm64 included in an updated commit in my fork https://github.com/wparr-circle/erpc/commit/cfe0627f855b1079f1d32461d7fcd04153326fd7, built locally and successfully running in Kubernetes with the security context options:
```
securityContext:
  runAsNonRoot: true
  runAsUser: 65532
```


author_association: CONTRIBUTOR
user: login: aramalipoor
created_at: 2025-04-24T12:41:43Z
body: Great idea and thanks for all the details. It totally makes sense to avoid root user + distroless, I don't see any downsides.

Re arm64 unfortunately one of libraries for efficient JSON parsing ([sonic](https://github.com/search?q=repo%3Abytedance%2Fsonic+arm64&type=issues)) doesn't seem to be working fully, explored that a bit in #231  For now decided to remove the arm64 build so people don't assume it would work with no issues.

I'll review and test your changes and create a PR, of course would be awesome to have your name as the actual contributor of this, let me know if we can do anything on the CLA for folks like yourself.


author_association: MEMBER

Diff:
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 0e04dee9b..a74613a3f 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -54,7 +54,7 @@ release:
 
     ```bash
     docker run \
-      -v $(pwd)/erpc.yaml:/root/erpc.yaml 
+      -v $(pwd)/erpc.yaml:/erpc.yaml 
       -p 4000:4000 -p 4001:4001 \
       ghcr.io/erpc/erpc:{{ .Version }}
     ```
diff --git a/Dockerfile b/Dockerfile
index fac842c57..0564a7fee 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,7 +11,7 @@
 # Build stage for Go
 FROM golang:1.23-alpine AS go-builder
 
-WORKDIR /root
+WORKDIR /build
 
 # Copy go mod and sum files first for better layer caching
 COPY go.mod go.sum ./
@@ -63,26 +63,26 @@ COPY package.json /temp/prod/package.json
 # Install every prod dependencies
 RUN --mount=type=cache,id=pnpm,target=/pnpm/store cd /temp/prod && pnpm install --prod --frozen-lockfile
 
-# Final stage
-FROM debian:stable AS final
-
-WORKDIR /root
+# Create symlink stage (for backwards compatibility with earlier image file structure)
+FROM alpine:latest AS symlink
+RUN mkdir -p /root && ln -s /erpc-server /root/erpc-server
 
-# Install CA certificates
-RUN apt-get update --allow-insecure-repositories \
-    && apt-get install -y debian-archive-keyring ca-certificates \
-    && rm -rf /var/lib/apt/lists/*
+# Final stage
+FROM gcr.io/distroless/static-debian12:nonroot AS final
 
 # Copy Go binary from go-builder
-COPY --from=go-builder /root/erpc-server .
-COPY --from=go-builder /root/erpc-server-pprof .
+COPY --from=go-builder /build/erpc-server /
+COPY --from=go-builder /build/erpc-server-pprof /
+
+# Copy symlinked directory with preserved symlinks
+COPY --from=symlink --link /root /root
 
 # Copy TypeScript package files from ts-dev and ts-prod
-COPY --from=ts-dev /temp/dev/typescript ./typescript
-COPY --from=ts-prod /temp/prod/node_modules ./node_modules
+COPY --from=ts-dev /temp/dev/typescript /typescript
+COPY --from=ts-prod /temp/prod/node_modules /node_modules
 
 # Expose ports
-EXPOSE 8080 6060
+EXPOSE 4000 4001 6060
 
 # Run the server
-CMD ["/root/erpc-server"]
\ No newline at end of file
+CMD ["/erpc-server"]
diff --git a/cmd/erpc/main.go b/cmd/erpc/main.go
index 0bb9dc407..57d6b5045 100644
--- a/cmd/erpc/main.go
+++ b/cmd/erpc/main.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"os"
 	"os/signal"
+	"path/filepath"
 	"syscall"
 
 	"github.com/erpc/erpc/common"
@@ -148,7 +149,22 @@ func getConfig(
 ) (*common.Config, error) {
 	fs := afero.NewOsFs()
 	configPath := ""
-	possibleConfigs := []string{"./erpc.js", "./erpc.ts", "./erpc.yaml", "./erpc.yml"}
+	possibleConfigs := []string{
+		"./erpc.yaml",
+		"./erpc.yml",
+		"./erpc.ts",
+		"./erpc.js",
+
+		"/erpc.yaml",
+		"/erpc.yml",
+		"/erpc.ts",
+		"/erpc.js",
+
+		"/root/erpc.yml",
+		"/root/erpc.yaml",
+		"/root/erpc.ts",
+		"/root/erpc.js",
+	}
 	requireConfig := cmd.Bool("require-config")
 
 	// Check for the config flag, if present, use that file
@@ -159,8 +175,14 @@ func getConfig(
 		configPath = cmd.Args().First()
 		requireConfig = true
 	} else { // Check for defaults config paths
+		currentDir, err := os.Getwd()
+		if err != nil {
+			return nil, fmt.Errorf("failed to get current directory: %v", err)
+		}
 		for _, path := range possibleConfigs {
-			if _, err := fs.Stat(path); err == nil {
+			fullPath := filepath.Join(currentDir, path)
+			logger.Info().Msgf("looking for config file: %s", fullPath)
+			if _, err := fs.Stat(fullPath); err == nil {
 				configPath = path
 				break
 			}
diff --git a/docs/pages/deployment/docker.mdx b/docs/pages/deployment/docker.mdx
index 7b014e066..392344ee4 100644
--- a/docs/pages/deployment/docker.mdx
+++ b/docs/pages/deployment/docker.mdx
@@ -59,7 +59,7 @@ export default createConfig({
 Run the Docker container, mounting your configuration file:
 
 ```bash
-docker run -v $(pwd)/erpc.yaml:/root/erpc.yaml \
+docker run -v $(pwd)/erpc.yaml:/erpc.yaml \
   -p 4000:4000 -p 4001:4001 \
   ghcr.io/erpc/erpc:latest
 ```
@@ -109,7 +109,7 @@ services:
       - "4000:4000"
       - "4001:4001"
     volumes:
-      - ./erpc.yaml:/root/erpc.yaml
+      - ./erpc.yaml:/erpc.yaml
     restart: unless-stopped
 ```
 
@@ -124,9 +124,9 @@ Create a custom Dockerfile that includes your dependencies:
 ```dockerfile
 FROM debian:12
 
-COPY package.json pnpm-lock.yaml /root/
-# COPY package.json package-lock.json /root/  # For npm
-# COPY package.json yarn.lock /root/          # For yarn
+COPY package.json pnpm-lock.yaml /
+# COPY package.json package-lock.json /  # For npm
+# COPY package.json yarn.lock /          # For yarn
 
 RUN pnpm install
 # RUN npm install   # For npm
@@ -134,14 +134,14 @@ RUN pnpm install
 
 FROM ghcr.io/erpc/erpc:latest
 
-COPY --from=0 /root/node_modules /root/node_modules
+COPY --from=0 /node_modules /node_modules
 ```
 
 Build and run your custom image:
 
 ```bash
 docker build -t erpc-custom -f Dockerfile.custom .
-docker run -v $(pwd)/erpc.ts:/root/erpc.ts \
+docker run -v $(pwd)/erpc.ts:/erpc.ts \
   -p 4000:4000 -p 4001:4001 \
   erpc-custom
 ```
@@ -152,9 +152,9 @@ Alternatively, you can mount your local `package.json` and `node_modules` direct
 
 ```bash
 docker run \
-  -v $(pwd)/package.json:/root/package.json \
-  -v $(pwd)/node_modules:/root/node_modules \
-  -v $(pwd)/erpc.ts:/root/erpc.ts \
+  -v $(pwd)/package.json:/package.json \
+  -v $(pwd)/node_modules:/node_modules \
+  -v $(pwd)/erpc.ts:/erpc.ts \
   -p 4000:4000 -p 4001:4001 \
   ghcr.io/erpc/erpc:latest
 ```
@@ -170,9 +170,9 @@ services:
       - "4000:4000"
       - "4001:4001"
     volumes:
-      - ./erpc.ts:/root/erpc.ts
-      - ./package.json:/root/package.json
-      - ./node_modules:/root/node_modules
+      - ./erpc.ts:/erpc.ts
+      - ./package.json:/package.json
+      - ./node_modules:/node_modules
     restart: unless-stopped
 ```
 
diff --git a/docs/pages/deployment/kubernetes.mdx b/docs/pages/deployment/kubernetes.mdx
index b29835c91..5f0457d34 100644
--- a/docs/pages/deployment/kubernetes.mdx
+++ b/docs/pages/deployment/kubernetes.mdx
@@ -80,7 +80,7 @@ spec:
           name: metrics
         volumeMounts:
         - name: config
-          mountPath: /root/erpc.yaml
+          mountPath: /erpc.yaml
           subPath: erpc.yaml
         readinessProbe:
           httpGet:
diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx
index 79493e115..db8aaa794 100644
--- a/docs/pages/index.mdx
+++ b/docs/pages/index.mdx
@@ -28,7 +28,7 @@ See [a complete config example](/config/example) for inspiration.
 Use the Docker image:
 
 ```bash
-docker run -v $(pwd)/erpc.yaml:/root/erpc.yaml -p 4000:4000 -p 4001:4001 ghcr.io/erpc/erpc:latest
+docker run -v $(pwd)/erpc.yaml:/erpc.yaml -p 4000:4000 -p 4001:4001 ghcr.io/erpc/erpc:latest
 ```
 
 ### Test the setup
@@ -75,4 +75,4 @@ Send more requests and watch the metrics being collected and visualized in Grafa
 
 ![eRPC Grafana Dashboard](/assets/monitoring-example-erpc.png)
 
-</Steps>
\ No newline at end of file
+</Steps>
diff --git a/docs/public/llms.txt b/docs/public/llms.txt
index 726aae437..fa262ccdd 100644
--- a/docs/public/llms.txt
+++ b/docs/public/llms.txt
@@ -1482,7 +1482,7 @@ See [a complete config example](https://docs.erpc.cloud/config/example) for insp
 Use the Docker image:
 
 ```nx-border-black nx-border-opacity-[0.04] nx-bg-opacity-[0.03] nx-bg-black nx-break-words nx-rounded-md nx-border nx-py-0.5 nx-px-[.25em] nx-text-[.9em] dark:nx-border-white/10 dark:nx-bg-white/10
-docker run -v $(pwd)/erpc.yaml:/root/erpc.yaml -p 4000:4000 -p 4001:4001 ghcr.io/erpc/erpc:latest
+docker run -v $(pwd)/erpc.yaml:/erpc.yaml -p 4000:4000 -p 4001:4001 ghcr.io/erpc/erpc:latest
 ```
 
 ### Test the setup [Permalink for this section](https://docs.erpc.cloud/\#test-the-setup)
@@ -3494,7 +3494,7 @@ See the [complete config example](https://docs.erpc.cloud/config/example) for al
 Run the Docker container, mounting your configuration file:
 
 ```nx-border-black nx-border-opacity-[0.04] nx-bg-opacity-[0.03] nx-bg-black nx-break-words nx-rounded-md nx-border nx-py-0.5 nx-px-[.25em] nx-text-[.9em] dark:nx-border-white/10 dark:nx-bg-white/10
-docker run -v $(pwd)/erpc.yaml:/root/erpc.yaml \
+docker run -v $(pwd)/erpc.yaml:/erpc.yaml \
   -p 4000:4000 -p 4001:4001 \
   ghcr.io/erpc/erpc:latest
 ```
@@ -3542,7 +3542,7 @@ services:
       - "4000:4000"
       - "4001:4001"
     volumes:
-      - ./erpc.yaml:/root/erpc.yaml
+      - ./erpc.yaml:/erpc.yaml
     restart: unless-stopped
 ```
 
@@ -3557,9 +3557,9 @@ Create a custom Dockerfile that includes your dependencies:
 ```nx-border-black nx-border-opacity-[0.04] nx-bg-opacity-[0.03] nx-bg-black nx-break-words nx-rounded-md nx-border nx-py-0.5 nx-px-[.25em] nx-text-[.9em] dark:nx-border-white/10 dark:nx-bg-white/10
 FROM debian:12
 
-COPY package.json pnpm-lock.yaml /root/
-# COPY package.json package-lock.json /root/  # For npm
-# COPY package.json yarn.lock /root/          # For yarn
+COPY package.json pnpm-lock.yaml /
+# COPY package.json package-lock.json /  # For npm
+# COPY package.json yarn.lock /          # For yarn
 
 RUN pnpm install
 # RUN npm install   # For npm
@@ -3567,14 +3567,14 @@ RUN pnpm install
 
 FROM ghcr.io/erpc/erpc:latest
 
-COPY --from=0 /root/node_modules /root/node_modules
+COPY --from=0 /node_modules /node_modules
 ```
 
 Build and run your custom image:
 
 ```nx-border-black nx-border-opacity-[0.04] nx-bg-opacity-[0.03] nx-bg-black nx-break-words nx-rounded-md nx-border nx-py-0.5 nx-px-[.25em] nx-text-[.9em] dark:nx-border-white/10 dark:nx-bg-white/10
 docker build -t erpc-custom -f Dockerfile.custom .
-docker run -v $(pwd)/erpc.ts:/root/erpc.ts \
+docker run -v $(pwd)/erpc.ts:/erpc.ts \
   -p 4000:4000 -p 4001:4001 \
   erpc-custom
 ```
@@ -3585,9 +3585,9 @@ Alternatively, you can mount your local `package.json` and `node_modules` direct
 
 ```nx-border-black nx-border-opacity-[0.04] nx-bg-opacity-[0.03] nx-bg-black nx-break-words nx-rounded-md nx-border nx-py-0.5 nx-px-[.25em] nx-text-[.9em] dark:nx-border-white/10 dark:nx-bg-white/10
 docker run \
-  -v $(pwd)/package.json:/root/package.json \
-  -v $(pwd)/node_modules:/root/node_modules \
-  -v $(pwd)/erpc.ts:/root/erpc.ts \
+  -v $(pwd)/package.json:/package.json \
+  -v $(pwd)/node_modules:/node_modules \
+  -v $(pwd)/erpc.ts:/erpc.ts \
   -p 4000:4000 -p 4001:4001 \
   ghcr.io/erpc/erpc:latest
 ```
@@ -3603,9 +3603,9 @@ services:
       - "4000:4000"
       - "4001:4001"
     volumes:
-      - ./erpc.ts:/root/erpc.ts
-      - ./package.json:/root/package.json
-      - ./node_modules:/root/node_modules
+      - ./erpc.ts:/erpc.ts
+      - ./package.json:/package.json
+      - ./node_modules:/node_modules
     restart: unless-stopped
 ```
 
@@ -4183,7 +4183,7 @@ spec:
           name: metrics
         volumeMounts:
         - name: config
-          mountPath: /root/erpc.yaml
+          mountPath: /erpc.yaml
           subPath: erpc.yaml
         readinessProbe:
           httpGet:
diff --git a/kube/erpc.yml b/kube/erpc.yml
index 4b7487035..bacadc2ec 100644
--- a/kube/erpc.yml
+++ b/kube/erpc.yml
@@ -26,7 +26,7 @@ spec:
       - name: main
         image: "ghcr.io/erpc/erpc:latest"
         imagePullPolicy: Always
-        command: ["/root/erpc-server"]
+        command: ["/erpc-server"]
         resources:
           requests:
             memory: "3Gi"
@@ -39,7 +39,7 @@ spec:
             value: "some value"
         volumeMounts:
           - name: config-volume
-            mountPath: /root/erpc.yaml
+            mountPath: /erpc.yaml
             subPath: erpc.yaml
       volumes:
       - name: config-volume
diff --git a/typescript/config/README.md b/typescript/config/README.md
index 82e0e54c6..908189c6e 100644
--- a/typescript/config/README.md
+++ b/typescript/config/README.md
@@ -53,7 +53,7 @@ export default createConfig({
 And then run eRPC with the config:
 
 ```bash
-docker run -v $(pwd)/erpc.ts:/root/erpc.ts -p 4000:4000 -p 4001:4001 ghcr.io/erpc/erpc:latest
+docker run -v $(pwd)/erpc.ts:/erpc.ts -p 4000:4000 -p 4001:4001 ghcr.io/erpc/erpc:latest
 ```
 
 ## Installing NPM dependencies
@@ -68,9 +68,9 @@ Create a `Dockerfile.custom` file:
 ```dockerfile
 FROM debian:12
 
-COPY package.json pnpm-lock.yaml /root/
-# COPY package.json package-lock.json /root/
-# COPY package.json yarn.lock /root/
+COPY package.json pnpm-lock.yaml /
+# COPY package.json package-lock.json /
+# COPY package.json yarn.lock /
 
 RUN pnpm install
 # RUN npm install
@@ -78,7 +78,7 @@ RUN pnpm install
 
 FROM ghcr.io/erpc/erpc:latest
 
-COPY --from=0 /root/node_modules /root/node_modules
+COPY --from=0 /node_modules /node_modules
 ```
 
 Then build the image:
@@ -92,7 +92,7 @@ In this case, you can use the `-v` flag to mount the package.json and node_modul
 
 ```bash
 docker run \
-    -v $(pwd)/package.json:/root/package.json \
-    -v $(pwd)/node_modules:/root/node_modules \
+    -v $(pwd)/package.json:/package.json \
+    -v $(pwd)/node_modules:/node_modules \
     -p 4000:4000 -p 4001:4001 ghcr.io/erpc/erpc:latest
 ```


Reviews:
user: login: aramalipoor
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-27T17:10:34Z
user: login: aramalipoor
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-27T17:10:50Z
user: login: aramalipoor
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-27T17:12:17Z
user: login: aramalipoor
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-27T17:26:18Z
user: login: aramalipoor
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-27T18:30:36Z

Review comments:
diff_hunk: @@ -68,17 +68,17 @@ Create a `Dockerfile.custom` file:
 ```dockerfile
 FROM debian:12
 
-COPY package.json pnpm-lock.yaml /root/
-# COPY package.json package-lock.json /root/
-# COPY package.json yarn.lock /root/
+COPY package.json pnpm-lock.yaml /
+# COPY package.json package-lock.json /
+# COPY package.json yarn.lock /
 
 RUN pnpm install
 # RUN npm install
 # RUN yarn install
 
 FROM ghcr.io/erpc/erpc:latest
 
-COPY --from=0 /root/node_modules /root/node_modules
+COPY --from=0 /root/node_modules /node_modules
path: typescript/config/README.md
user: login: aramalipoor
body: Should we remove /root here as well? 
```suggestion
COPY --from=0 /node_modules /node_modules
```
created_at: 2025-04-27T17:10:34Z
author_association: MEMBER
diff_hunk: @@ -3557,24 +3557,24 @@ Create a custom Dockerfile that includes your dependencies:
 ```nx-border-black nx-border-opacity-[0.04] nx-bg-opacity-[0.03] nx-bg-black nx-break-words nx-rounded-md nx-border nx-py-0.5 nx-px-[.25em] nx-text-[.9em] dark:nx-border-white/10 dark:nx-bg-white/10
 FROM debian:12
 
-COPY package.json pnpm-lock.yaml /root/
-# COPY package.json package-lock.json /root/  # For npm
-# COPY package.json yarn.lock /root/          # For yarn
+COPY package.json pnpm-lock.yaml /
+# COPY package.json package-lock.json /  # For npm
+# COPY package.json yarn.lock /          # For yarn
 
 RUN pnpm install
 # RUN npm install   # For npm
 # RUN yarn install  # For yarn
 
 FROM ghcr.io/erpc/erpc:latest
 
-COPY --from=0 /root/node_modules /root/node_modules
+COPY --from=0 /root/node_modules /node_modules
path: docs/public/llms.txt
user: login: aramalipoor
body: Should we remove /root here as well? 
```suggestion
COPY --from=0 /node_modules /node_modules
```
created_at: 2025-04-27T17:10:50Z
author_association: MEMBER
diff_hunk: @@ -64,25 +64,18 @@ COPY package.json /temp/prod/package.json
 RUN --mount=type=cache,id=pnpm,target=/pnpm/store cd /temp/prod && pnpm install --prod --frozen-lockfile
 
 # Final stage
-FROM debian:stable AS final
-
-WORKDIR /root
-
-# Install CA certificates
-RUN apt-get update --allow-insecure-repositories \
-    && apt-get install -y debian-archive-keyring ca-certificates \
-    && rm -rf /var/lib/apt/lists/*
+FROM gcr.io/distroless/static-debian12:nonroot AS final
 
 # Copy Go binary from go-builder
-COPY --from=go-builder /root/erpc-server .
-COPY --from=go-builder /root/erpc-server-pprof .
+COPY --from=go-builder /root/erpc-server /
path: Dockerfile
user: login: aramalipoor
body: Update this whole Dockerfile to never use /root at all, even the WORKDIR
created_at: 2025-04-27T17:12:17Z
author_association: MEMBER
diff_hunk: @@ -64,25 +64,18 @@ COPY package.json /temp/prod/package.json
 RUN --mount=type=cache,id=pnpm,target=/pnpm/store cd /temp/prod && pnpm install --prod --frozen-lockfile
 
 # Final stage
-FROM debian:stable AS final
-
-WORKDIR /root
-
-# Install CA certificates
-RUN apt-get update --allow-insecure-repositories \
-    && apt-get install -y debian-archive-keyring ca-certificates \
-    && rm -rf /var/lib/apt/lists/*
+FROM gcr.io/distroless/static-debian12:nonroot AS final
 
 # Copy Go binary from go-builder
-COPY --from=go-builder /root/erpc-server .
-COPY --from=go-builder /root/erpc-server-pprof .
+COPY --from=go-builder /build/erpc-server /
+COPY --from=go-builder /build/erpc-server-pprof /
 
 # Copy TypeScript package files from ts-dev and ts-prod
-COPY --from=ts-dev /temp/dev/typescript ./typescript
-COPY --from=ts-prod /temp/prod/node_modules ./node_modules
+COPY --from=ts-dev /temp/dev/typescript /typescript
+COPY --from=ts-prod /temp/prod/node_modules /node_modules
 
 # Expose ports
-EXPOSE 8080 6060
+EXPOSE 8080 6060 4000
path: Dockerfile
user: login: aramalipoor
body: ```suggestion
EXPOSE 4000 4001 6060
```
These are correct default ports
created_at: 2025-04-27T17:26:18Z
author_association: MEMBER
diff_hunk: @@ -63,26 +63,26 @@ COPY package.json /temp/prod/package.json
 # Install every prod dependencies
 RUN --mount=type=cache,id=pnpm,target=/pnpm/store cd /temp/prod && pnpm install --prod --frozen-lockfile
 
-# Final stage
-FROM debian:stable AS final
-
-WORKDIR /root
+# Create symlink stage
+FROM alpine:latest AS symlink
+RUN mkdir -p /root && ln -s /erpc-server /root/erpc-server
 
-# Install CA certificates
-RUN apt-get update --allow-insecure-repositories \
-    && apt-get install -y debian-archive-keyring ca-certificates \
-    && rm -rf /var/lib/apt/lists/*
+# Final stage
+FROM gcr.io/distroless/static-debian12:nonroot AS final
 
 # Copy Go binary from go-builder
-COPY --from=go-builder /root/erpc-server .
-COPY --from=go-builder /root/erpc-server-pprof .
+COPY --from=go-builder /build/erpc-server /
+COPY --from=go-builder /build/erpc-server-pprof /
+
+# Copy symlinked directory
+COPY --from=symlink /root /root
path: Dockerfile
user: login: aramalipoor
body: This doesn't seem correct, we need a symlink on /root/erpc-server -> /erpc-server in the final image
created_at: 2025-04-27T18:30:36Z
author_association: MEMBER

PR comments:
user: login: CLAassistant
created_at: 2025-04-27T16:46:23Z
body: [![CLA assistant check](https://cla-assistant.io/pull/badge/signed)](https://cla-assistant.io/erpc/erpc?pullRequest=244) <br/>All committers have signed the CLA.
author_association: NONE
user: login: devin-ai-integration[bot]
created_at: 2025-04-27T16:46:24Z
body: ### 🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:
- Address comments on this PR that start with '@devin'.
- Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:
- [ ] Disable automatic comment and CI monitoring

author_association: CONTRIBUTOR
user: login: aramalipoor
created_at: 2025-04-27T16:49:14Z
body: @devin-ai-integration find all occurrences of /root in the docs and other files (e.g. docker-compose) and change accordingly.
author_association: MEMBER
user: login: aramalipoor
created_at: 2025-04-27T18:18:52Z
body: Update the LoadConfig in config.go and the Dockerfile image to be backward compatible:
- A symlink created in /root/erpc-server to /erpc-server
- Add /root/erpc.yaml as one of places to look for config 
author_association: MEMBER