Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changes

- Store pending blocks separately from executed blocks key. [#3073](https://github.com/evstack/ev-node/pull/3073)
- **BREAKING:** Docker images for `evm`, `testapp`, and `local-da` now run as non-root user `ev-node` (uid 1000) instead of `root`. Existing volumes or bind mounts with root-owned files may require a `chown` to uid 1000. See the [migration guide](https://ev.xyz/guides/migrate-docker-nonroot).

## v1.0.0-rc.4

Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ include ./scripts/test.mk
include ./scripts/proto.mk
include ./scripts/utils.mk
include ./scripts/run.mk
include ./scripts/security.mk
include ./tools/tools.mk

# Sets the default make target to `build`.
Expand Down
10 changes: 8 additions & 2 deletions apps/evm/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@ FROM alpine:3.22.2
#hadolint ignore=DL3018
RUN apk --no-cache add ca-certificates curl

WORKDIR /root
RUN addgroup -g 1000 ev-node && \
adduser -u 1000 -G ev-node -s /bin/sh -D ev-node
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The adduser command uses the -D flag, which prevents the creation of a home directory. However, the subsequent WORKDIR /home/ev-node implies that /home/ev-node is intended to be the user's home directory. For consistency with apps/testapp/Dockerfile and clearer intent, it's better to allow adduser to create the home directory by removing the -D flag, or explicitly create it if -D is strictly necessary for other reasons. Removing -D is the most straightforward approach to align with the WORKDIR and chown commands.

    adduser -u 1000 -G ev-node -s /bin/sh ev-node


WORKDIR /home/ev-node

COPY --from=build-env /src/apps/evm/evm /usr/bin/evm
COPY apps/evm/entrypoint.sh /usr/bin/entrypoint.sh
RUN chmod +x /usr/bin/entrypoint.sh
RUN chmod +x /usr/bin/entrypoint.sh && \
chown -R ev-node:ev-node /home/ev-node

USER ev-node

ENTRYPOINT ["/usr/bin/entrypoint.sh"]
8 changes: 7 additions & 1 deletion apps/testapp/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM golang:1.25 AS base

#hadolint ignore=DL3018
RUN apt-get update && \

Check failure on line 4 in apps/testapp/Dockerfile

View workflow job for this annotation

GitHub Actions / lint / hadolint

DL3008 warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
Expand All @@ -27,8 +27,14 @@
#
FROM base

RUN groupadd -g 1000 ev-node && \
useradd -u 1000 -g ev-node -s /bin/sh -m ev-node

COPY --from=builder /go/bin/testapp /usr/bin

WORKDIR /apps
WORKDIR /home/ev-node
RUN chown -R ev-node:ev-node /home/ev-node

USER ev-node

ENTRYPOINT ["testapp"]
4 changes: 4 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ function sidebarHome() {
text: "Migrating to ev-abci",
link: "/guides/migrating-to-ev-abci",
},
{
text: "Migrate Docker to non-root",
link: "/guides/migrate-docker-nonroot",
},
{
text: "Create genesis for your chain",
link: "/guides/create-genesis",
Expand Down
112 changes: 112 additions & 0 deletions docs/guides/migrate-docker-nonroot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Migrating Docker Containers to Non-Root User

Starting with this release, the `evm`, `testapp`, and `local-da` Docker images run as a non-root user (`ev-node`, uid/gid 1000) instead of `root`. This aligns with the `grpc` image, which already ran as non-root.

If you are running any of these containers with **persistent volumes or bind mounts**, you need to fix file ownership before upgrading. Containers running without persistent storage (ephemeral) require no action.

## Who is affected

You are affected if **all** of the following are true:

- You run `evm`, `testapp`, or `local-da` via Docker (or docker-compose / Kubernetes)
- You use a volume or bind mount for the container's data directory
- The files in that volume were created by a previous (root-based) image

## Migration steps

### 1. Stop the running container

```bash
docker stop <container-name>
```

### 2. Fix file ownership on the volume

For **bind mounts** (host directory), run `chown` directly on the host:

```bash
# Replace /path/to/data with your actual data directory
sudo chown -R 1000:1000 /path/to/data
```

For **named Docker volumes**, use a temporary container:

```bash
# Replace <volume-name> with your Docker volume name
docker run --rm -v <volume-name>:/data alpine chown -R 1000:1000 /data
```

### 3. Pull the new image and restart

```bash
docker pull <image>
docker start <container-name>
```

### Kubernetes / docker-compose

If you manage containers through orchestration, you have two options:

**Option A: Init container (recommended for Kubernetes)**

Add an init container that fixes ownership before the main container starts:

```yaml
initContainers:
- name: fix-permissions
image: alpine:3.22
command: ["chown", "-R", "1000:1000", "/home/ev-node"]
volumeMounts:
- name: data
mountPath: /home/ev-node
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
```

**Option B: Set `fsGroup` in the pod security context**

If your volume driver supports it, setting `fsGroup: 1000` will automatically fix ownership on mount:

```yaml
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
```

**docker-compose**: update your `docker-compose.yml` to set the user:

```yaml
services:
evm:
image: evm:latest
user: "1000:1000"
volumes:
- evm-data:/home/ev-node
```

## Verifying the migration

After restarting, confirm the container runs as the correct user:

```bash
docker exec <container-name> id
# Expected: uid=1000(ev-node) gid=1000(ev-node)
```

Check that the process can read and write its data directory:

```bash
docker exec <container-name> ls -la /home/ev-node
# All files should be owned by ev-node:ev-node
```

## Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| `Permission denied` on startup | Volume files still owned by root | Re-run the `chown` step above |
| Container exits immediately | Data directory not writable | Check ownership and directory permissions (`drwxr-xr-x` or more permissive for uid 1000) |
| Application writes to wrong path | Old `WORKDIR` was `/root` or `/apps` | Update any custom volume mounts to target `/home/ev-node` instead |
43 changes: 43 additions & 0 deletions scripts/security.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# security.mk - Security scanning with Trivy (https://trivy.dev)

TRIVY_IMAGE := aquasec/trivy:latest
TRIVY_SEVERITY ?= CRITICAL,HIGH
TRIVY_CACHE_VOLUME := trivy-cache

# Docker images to scan (space-separated, override or extend as needed)
SCAN_IMAGES ?= evstack:local-dev
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The SCAN_IMAGES variable defaults to evstack:local-dev. While the comment indicates it can be overridden, having a single specific image as the default might lead to other relevant images being missed during scans if the user doesn't explicitly configure this variable. Consider making this variable empty by default or providing a more generic placeholder, encouraging users to define the images they intend to scan, or adding a clear example of how to extend it for multiple images.


# Common docker run args for Trivy
TRIVY_RUN := docker run --rm \
-v $(TRIVY_CACHE_VOLUME):/root/.cache/ \
-e TRIVY_SEVERITY=$(TRIVY_SEVERITY)

## trivy-scan: Run all Trivy security scans (filesystem + Docker images)
trivy-scan: trivy-scan-fs trivy-scan-image
.PHONY: trivy-scan

## trivy-scan-fs: Scan repo for dependency vulnerabilities, misconfigs, and secrets
trivy-scan-fs:
@echo "--> Scanning repository filesystem with Trivy"
@$(TRIVY_RUN) \
-v $(CURDIR):/workspace \
$(TRIVY_IMAGE) \
fs --scanners vuln,misconfig,secret \
--severity $(TRIVY_SEVERITY) \
/workspace
@echo "--> Filesystem scan complete"
.PHONY: trivy-scan-fs

## trivy-scan-image: Scan built Docker images for vulnerabilities
trivy-scan-image:
@echo "--> Scanning Docker images with Trivy"
@for img in $(SCAN_IMAGES); do \
echo "--> Scanning image: $$img"; \
$(TRIVY_RUN) \
-v /var/run/docker.sock:/var/run/docker.sock \
$(TRIVY_IMAGE) \
image --severity $(TRIVY_SEVERITY) \
$$img; \
done
@echo "--> Image scan complete"
.PHONY: trivy-scan-image
8 changes: 7 additions & 1 deletion tools/local-da/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ FROM alpine:3.22.2
#hadolint ignore=DL3018
RUN apk --no-cache add ca-certificates curl

WORKDIR /root
RUN addgroup -g 1000 ev-node && \
adduser -u 1000 -G ev-node -s /bin/sh -D ev-node

WORKDIR /home/ev-node
RUN chown -R ev-node:ev-node /home/ev-node

COPY --from=build-env /src/build/local-da /usr/bin/local-da

USER ev-node

ENTRYPOINT ["/usr/bin/local-da"]
CMD ["-listen-all"]
Loading