Skip to content

Commit 9ad3720

Browse files
authored
Merge pull request #1 from nayrosk/feat/improve_build_system
feat: harden Docker image, distroless, rootless, multi-arch
2 parents 771e2f4 + 6800e22 commit 9ad3720

File tree

3 files changed

+190
-43
lines changed

3 files changed

+190
-43
lines changed

Dockerfile

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,73 @@
1-
FROM golang:1.23.6 AS builder
1+
# ---------------------------------------------------------------------------
2+
# Stage 1: Fetch configs
3+
# ---------------------------------------------------------------------------
4+
FROM --platform=$BUILDPLATFORM golang:1.23.6-bookworm AS config-fetcher
25

3-
4-
WORKDIR /multiversx
6+
WORKDIR /src
57
COPY . .
68

7-
RUN go mod tidy
9+
WORKDIR /src/cmd/chainsimulator
10+
RUN go build -o chainsimulator \
11+
&& ./chainsimulator --fetch-configs-and-close
812

9-
WORKDIR /multiversx/cmd/chainsimulator
13+
# ---------------------------------------------------------------------------
14+
# Stage 2: Build the binary + extract Wasmer libs
15+
# ---------------------------------------------------------------------------
16+
FROM golang:1.23.6-bookworm AS builder
1017

11-
RUN go build -o chainsimulator
18+
WORKDIR /src
19+
COPY . .
1220

13-
RUN mkdir -p /lib_amd64 /lib_arm64
21+
# Download all modules
22+
RUN go mod download
1423

15-
RUN cp /go/pkg/mod/github.com/multiversx/$(cat /multiversx/go.sum | grep mx-chain-vm-v | sort -n | tail -n -1 | awk -F '/' '{print$3}' | sed 's/ /@/g')/wasmer/libwasmer_linux_amd64.so /lib_amd64/
16-
RUN cp /go/pkg/mod/github.com/multiversx/$(cat /multiversx/go.sum | grep mx-chain-vm-go | sort -n | tail -n -1 | awk -F '/' '{print$3}' | sed 's/ /@/g')/wasmer2/libvmexeccapi.so /lib_amd64/
24+
WORKDIR /src/cmd/chainsimulator
1725

18-
RUN cp /go/pkg/mod/github.com/multiversx/$(cat /multiversx/go.sum | grep mx-chain-vm-v | sort -n | tail -n -1 | awk -F '/' '{print$3}' | sed 's/ /@/g')/wasmer/libwasmer_linux_arm64_shim.so /lib_arm64/
19-
RUN cp /go/pkg/mod/github.com/multiversx/$(cat /multiversx/go.sum | grep mx-chain-vm-go | sort -n | tail -n -1 | awk -F '/' '{print$3}' | sed 's/ /@/g')/wasmer2/libvmexeccapi_arm.so /lib_arm64/
26+
# Build with optimizations: strip debug symbols, smaller binary
27+
# CGO_ENABLED=1 is implicit and required by Wasmer bindings.
28+
RUN go build \
29+
-ldflags="-s -w" \
30+
-trimpath \
31+
-o /out/chainsimulator
2032

33+
# ---------------------------------------------------------------------------
34+
# Extract architecture-specific Wasmer shared libraries
35+
# ---------------------------------------------------------------------------
36+
RUN mkdir -p /out/lib
2137

22-
FROM ubuntu:22.04
23-
ARG TARGETARCH
24-
RUN apt-get update && apt-get install -y git curl
38+
RUN cp /go/pkg/mod/github.com/multiversx/$(cat /src/go.sum \
39+
| grep mx-chain-vm-v | sort -n | tail -n -1 \
40+
| awk -F '/' '{print$3}' | sed 's/ /@/g')/wasmer/libwasmer_linux_$(dpkg --print-architecture | sed 's/arm64/arm64_shim/').so \
41+
/out/lib/ 2>/dev/null || true
2542

26-
COPY --from=builder /multiversx/cmd/chainsimulator /multiversx
43+
RUN cp /go/pkg/mod/github.com/multiversx/$(cat /src/go.sum \
44+
| grep mx-chain-vm-go | sort -n | tail -n -1 \
45+
| awk -F '/' '{print$3}' | sed 's/ /@/g')/wasmer2/libvmexeccapi$(dpkg --print-architecture | sed 's/amd64//;s/arm64/_arm/').so \
46+
/out/lib/ 2>/dev/null || true
2747

28-
EXPOSE 8085
48+
# ---------------------------------------------------------------------------
49+
# Stage 3: Minimal runtime image (distroless)
50+
# ---------------------------------------------------------------------------
51+
FROM gcr.io/distroless/cc-debian13:nonroot
2952

30-
WORKDIR /multiversx
53+
LABEL org.opencontainers.image.title="mx-chain-simulator-go" \
54+
org.opencontainers.image.description="MultiversX Chain Simulator" \
55+
org.opencontainers.image.source="https://github.com/multiversx/mx-chain-simulator-go" \
56+
org.opencontainers.image.licenses="GPL-3.0"
3157

32-
# Copy architecture-specific files
33-
COPY --from=builder "/lib_${TARGETARCH}/*" "/lib/"
58+
# Copy binary
59+
COPY --from=builder --chown=nonroot:nonroot /out/chainsimulator /app/chainsimulator
3460

35-
CMD ["/bin/bash"]
61+
# Copy pre-fetched configs
62+
COPY --from=config-fetcher --chown=nonroot:nonroot /src/cmd/chainsimulator/config /app/config
3663

37-
ENTRYPOINT ["./chainsimulator"]
64+
# Copy Wasmer libs
65+
COPY --from=builder /out/lib/ /lib/
3866

67+
WORKDIR /app
68+
EXPOSE 8085
69+
70+
# Run as non-root for security (UID 65532 is "nonroot" in distroless)
71+
USER nonroot:nonroot
3972

73+
ENTRYPOINT ["./chainsimulator"]

Makefile

Lines changed: 99 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,123 @@
1-
CHAIN_SIMULATOR_IMAGE_NAME=chainsimulator
2-
CHAIN_SIMULATOR_IMAGE_TAG=latest
3-
DOCKER_FILE=Dockerfile
4-
IMAGE_NAME=simulator_image
1+
IMAGE_NAME ?= chainsimulator
2+
IMAGE_TAG ?= latest
3+
REGISTRY ?= multiversx
4+
DOCKER_FILE ?= Dockerfile
5+
PLATFORMS ?= linux/amd64,linux/arm64
6+
CONTAINER_NAME ?= simulator_instance
7+
FULL_IMAGE = $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
58

9+
## Build the Go binary locally
10+
.PHONY: build
11+
build:
12+
cd cmd/chainsimulator && go build -ldflags="-s -w" -trimpath -o chainsimulator
13+
14+
## Fetch configs locally
15+
.PHONY: fetch-configs
16+
fetch-configs: build
17+
cd cmd/chainsimulator && ./chainsimulator --fetch-configs-and-close
18+
19+
## Build Docker image (single arch, current platform)
20+
.PHONY: docker-build
621
docker-build:
7-
docker build \
8-
-t ${CHAIN_SIMULATOR_IMAGE_NAME}:${CHAIN_SIMULATOR_IMAGE_TAG} \
9-
-f ${DOCKER_FILE} \
10-
.
22+
DOCKER_BUILDKIT=1 docker build \
23+
-t $(FULL_IMAGE) \
24+
-f $(DOCKER_FILE) \
25+
.
1126

12-
run-faucet-test:
13-
$(MAKE) docker-build
14-
docker run -d --name "${IMAGE_NAME}" -p 8085:8085 ${CHAIN_SIMULATOR_IMAGE_NAME}:${CHAIN_SIMULATOR_IMAGE_TAG}
27+
## Build multi-arch image and push to registry (requires login)
28+
## This is the correct way to produce a multi-platform manifest.
29+
.PHONY: docker-build-push
30+
docker-build-push:
31+
docker buildx build \
32+
--platform $(PLATFORMS) \
33+
-t $(FULL_IMAGE) \
34+
-f $(DOCKER_FILE) \
35+
--push \
36+
.
37+
38+
## Register QEMU for cross-platform builds (run once per boot)
39+
.PHONY: qemu-setup
40+
qemu-setup:
41+
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
42+
43+
## Run the simulator container
44+
.PHONY: docker-run
45+
docker-run:
46+
docker run -d \
47+
--name "$(CONTAINER_NAME)" \
48+
--read-only \
49+
--tmpfs /tmp \
50+
-p 8085:8085 \
51+
$(FULL_IMAGE)
52+
53+
## Stop and remove the simulator container
54+
.PHONY: docker-stop
55+
docker-stop:
56+
docker stop "$(CONTAINER_NAME)" 2>/dev/null || true
57+
docker rm "$(CONTAINER_NAME)" 2>/dev/null || true
58+
59+
## Run faucet example test
60+
.PHONY: run-faucet-test
61+
run-faucet-test: docker-build
62+
docker run -d --name "$(CONTAINER_NAME)" -p 8085:8085 $(FULL_IMAGE)
1563
sleep 2s
1664
cd examples/faucet && /bin/bash faucet.sh
17-
docker stop "${IMAGE_NAME}"
18-
docker rm ${IMAGE_NAME} 2> /dev/null
65+
$(MAKE) docker-stop
1966

67+
## Run all examples
68+
.PHONY: run-examples
2069
run-examples:
2170
printf '%s\n' '{ File = "enableEpochs.toml", Path = "EnableEpochs.StakeLimitsEnableEpoch", Value = 1000000 },' > temp.txt
2271
sed -i '4r temp.txt' cmd/chainsimulator/config/nodeOverrideDefault.toml
2372
rm temp.txt
24-
2573
$(MAKE) docker-build
26-
docker run -d --name "${IMAGE_NAME}" -p 8085:8085 ${CHAIN_SIMULATOR_IMAGE_NAME}:${CHAIN_SIMULATOR_IMAGE_TAG}
74+
docker run -d --name "$(CONTAINER_NAME)" -p 8085:8085 $(FULL_IMAGE)
2775
cd scripts/run-examples && /bin/bash install-python-deps.sh && /bin/bash script.sh
28-
docker stop "${IMAGE_NAME}"
29-
docker rm ${IMAGE_NAME}
76+
$(MAKE) docker-stop
3077

78+
## Install golint if not already present
79+
.PHONY: lint-install
3180
lint-install:
3281
ifeq (,$(wildcard test -f bin/golangci-lint))
3382
@echo "Installing golint"
3483
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s
3584
endif
3685

86+
## Run golint on the codebase
87+
.PHONY: run-lint
3788
run-lint:
3889
@echo "Running golint"
3990
bin/golangci-lint run --max-issues-per-linter 0 --max-same-issues 0 --timeout=2m
4091

92+
## Run lint installation and then the linter
93+
.PHONY: lint
4194
lint: lint-install run-lint
95+
96+
## Show image details
97+
.PHONY: docker-info
98+
docker-info:
99+
@echo "Image: $(FULL_IMAGE)"
100+
@echo "Dockerfile: $(DOCKER_FILE)"
101+
@echo "Platforms: $(PLATFORMS)"
102+
@docker images $(FULL_IMAGE) --format "Size: {{.Size}}"
103+
104+
## Run security scan with trivy (if installed)
105+
.PHONY: docker-scan
106+
docker-scan:
107+
trivy image --severity HIGH,CRITICAL $(FULL_IMAGE)
108+
109+
## Show available targets
110+
.PHONY: help
111+
help:
112+
@echo "Available targets:"
113+
@echo " build Build Go binary locally"
114+
@echo " fetch-configs Fetch configs from GitHub repos"
115+
@echo " docker-build Build Docker image (current platform, docker build)"
116+
@echo " docker-build-push Build & push multi-arch image to registry"
117+
@echo " qemu-setup Register QEMU for cross-arch builds"
118+
@echo " docker-run Run the simulator container (read-only)"
119+
@echo " docker-stop Stop and remove container"
120+
@echo " docker-info Show image details"
121+
@echo " docker-scan Trivy security scan"
122+
@echo " lint Run linter"
123+
@echo " help Show this help"

README.md

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,10 @@ This endpoint resets (clears) an internal cache used by the `/validator/statisti
420420

421421
Before proceeding, ensure you have the following prerequisites:
422422

423-
- Go programming environment set up.
423+
- Go 1.23.x programming environment set up.
424424
- Git installed.
425+
- Docker with BuildKit support (for container builds).
426+
- Docker Buildx (for multi-arch builds).
425427

426428

427429
## Install
@@ -431,6 +433,11 @@ Using the `cmd/chainsimulator` package as root, execute the following commands:
431433
- install go dependencies: `go install`
432434
- build executable: `go build -o chainsimulator`
433435

436+
Alternatively, use the Makefile:
437+
```
438+
make build
439+
```
440+
434441
Note: go version 1.23.* should be used to build the executable.
435442

436443

@@ -513,14 +520,38 @@ INFO [2024-04-18 10:48:47.231] chain simulator's is accessible through the URL
513520
```
514521

515522

516-
### Build docker image
523+
### Docker
524+
525+
The Docker image uses a multi-stage build with a [distroless](https://github.com/GoogleContainerTools/distroless) runtime image (`gcr.io/distroless/cc-debian13`). The container runs as a non-root user (UID 65532) for security. Configs are pre-fetched at build time so `git` and `curl` are not required at runtime.
526+
527+
#### Build (single arch, current platform)
528+
```
529+
make docker-build
530+
```
531+
532+
#### Build & push multi-arch (amd64 + arm64)
533+
```
534+
make docker-build-push
535+
```
536+
537+
For cross-platform builds from an amd64 host, register QEMU first (once per boot):
538+
```
539+
make qemu-setup
540+
```
541+
542+
#### Run
543+
```
544+
make docker-run
545+
```
546+
547+
The container is started in read-only mode with a tmpfs on `/tmp`. To pass extra flags:
517548
```
518-
DOCKER_BUILDKIT=1 docker build -t chainsimulator:latest .
549+
docker run -p 8085:8085 multiversx/chainsimulator:latest --log-level *:DEBUG
519550
```
520551

521-
### Run with docker
552+
#### Available Makefile targets
522553
```
523-
docker run -p 8085:8085 chainsimulator:latest --log-level *:DEBUG
554+
make help
524555
```
525556

526557
### Enable `HostDriver`

0 commit comments

Comments
 (0)