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
20 changes: 17 additions & 3 deletions .github/workflows/hadolint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,34 @@ jobs:
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
strategy:
fail-fast: false
matrix:
include:
- name: manager
dockerfile: ./docker/Dockerfile
output: hadolint-results-manager.sarif
- name: agent
dockerfile: ./docker/Dockerfile-agent
output: hadolint-results-agent.sarif
- name: dhi-base
dockerfile: ./docker/Dockerfile-dhi-base
output: hadolint-results-dhi-base.sarif
steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Run hadolint
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5
with:
dockerfile: ./docker/Dockerfile
dockerfile: ${{ matrix.dockerfile }}
format: sarif
output-file: hadolint-results.sarif
output-file: ${{ matrix.output }}
no-fail: true

- name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: hadolint-results.sarif
sarif_file: ${{ matrix.output }}
category: hadolint-${{ matrix.name }}
wait-for-processing: true
4 changes: 2 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -908,12 +908,12 @@ _build-backend:
# Build manager container image
[group('build')]
_build-image-manager tag="ghcr.io/getarcaneapp/arcane:development" flag='':
docker buildx build {{ if flag == "--push" { "--push" } else { "" } }} --platform linux/arm64,linux/amd64,linux/arm/v7 -f 'docker/Dockerfile' --build-arg ENABLED_FEATURES="{{ env('ENABLED_FEATURES', env('BUILD_FEATURES', '')) }}" -t "{{ tag }}" .
docker buildx build {{ if flag == "--push" { "--push" } else { "" } }} --platform "{{ env('BUILD_PLATFORMS', 'linux/arm64,linux/amd64') }}" -f 'docker/Dockerfile' --build-arg ENABLED_FEATURES="{{ env('ENABLED_FEATURES', env('BUILD_FEATURES', '')) }}" -t "{{ tag }}" .

# Build agent container image
[group('build')]
_build-image-agent tag="ghcr.io/getarcaneapp/arcane-headless:development" flag='':
docker buildx build {{ if flag == "--push" { "--push" } else { "" } }} --platform linux/arm64,linux/amd64,linux/arm/v7 -f 'docker/Dockerfile-agent' --build-arg ENABLED_FEATURES="{{ env('ENABLED_FEATURES', env('BUILD_FEATURES', '')) }}" -t "{{ tag }}" .
docker buildx build {{ if flag == "--push" { "--push" } else { "" } }} --platform "{{ env('BUILD_PLATFORMS', 'linux/arm64,linux/amd64') }}" -f 'docker/Dockerfile-agent' --build-arg ENABLED_FEATURES="{{ env('ENABLED_FEATURES', env('BUILD_FEATURES', '')) }}" -t "{{ tag }}" .

# Build both frontend and backend
[group('build')]
Expand Down
1 change: 0 additions & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/getarcaneapp/arcane/backend
go 1.26.2

replace github.com/getarcaneapp/arcane/cli => ../cli

replace github.com/getarcaneapp/arcane/types => ../types

require (
Expand Down
12 changes: 12 additions & 0 deletions backend/pkg/libarcane/startup/runtime_identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const (
defaultBuildsDirectory = "/builds"
defaultDatabaseURL = "file:data/arcane.db?_pragma=journal_mode(WAL)&_pragma=busy_timeout(2500)&_txlock=immediate"
defaultDockerSocketPath = "/var/run/docker.sock"
defaultRuntimeUID = 65532
defaultRuntimeGID = 65532
mountInfoPath = "/proc/self/mountinfo"
)

Expand Down Expand Up @@ -70,6 +72,16 @@ func loadRuntimeIdentityRequestInternal(getenv func(string) string) (runtimeIden
pgid := strings.TrimSpace(getenv("PGID"))

if puid == "" && pgid == "" {
if strings.EqualFold(strings.TrimSpace(getenv("ARCANE_DEFAULT_NONROOT")), "true") {
return runtimeIdentityRequest{
Enabled: true,
UID: defaultRuntimeUID,
GID: defaultRuntimeGID,
CredentialUID: uint32(defaultRuntimeUID),
CredentialGID: uint32(defaultRuntimeGID),
}, "", nil
}

return runtimeIdentityRequest{}, "", nil
}

Expand Down
16 changes: 16 additions & 0 deletions backend/pkg/libarcane/startup/runtime_identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ func TestLoadRuntimeIdentityRequest(t *testing.T) {
require.False(t, req.Enabled)
})

t.Run("enabled with default non-root image flag", func(t *testing.T) {
req, warning, err := loadRuntimeIdentityRequestInternal(func(key string) string {
if key == "ARCANE_DEFAULT_NONROOT" {
return "true"
}
return ""
})
require.NoError(t, err)
require.Empty(t, warning)
require.True(t, req.Enabled)
require.Equal(t, defaultRuntimeUID, req.UID)
require.Equal(t, defaultRuntimeGID, req.GID)
require.Equal(t, uint32(defaultRuntimeUID), req.CredentialUID)
require.Equal(t, uint32(defaultRuntimeGID), req.CredentialGID)
})

t.Run("warning when partial config", func(t *testing.T) {
req, warning, err := loadRuntimeIdentityRequestInternal(func(key string) string {
if key == "PUID" {
Expand Down
21 changes: 10 additions & 11 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# syntax=docker/dockerfile:1
ARG BUILD_TAGS=""
ARG ARCANE_RUNTIME_BASE_IMAGE=ghcr.io/getarcaneapp/base:trixie

# Stage 1: Build Frontend
FROM --platform=$BUILDPLATFORM node:25-trixie-slim AS frontend-builder
Expand Down Expand Up @@ -65,23 +66,18 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
-o /build/arcane \
./cmd/main.go

# Stage 3: Production Image
FROM debian:trixie-slim AS runner
# Stage 3: Hardened Production Image
FROM ${ARCANE_RUNTIME_BASE_IMAGE} AS runner-hardened

ARG TARGETARCH
ARG VERSION
ARG REVISION

RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl tzdata tar gzip \
vainfo clinfo libdrm2 libsystemd0 \
&& if [ "$TARGETARCH" = "amd64" ]; then \
apt-get install -y --no-install-recommends intel-gpu-tools; \
fi \
&& apt-get clean && rm -rf /var/lib/apt/lists/*

# keep the same env/settings as existing runner
ENV GIN_MODE=release
ENV PORT=3552
ENV ENVIRONMENT=production
ENV ARCANE_DEFAULT_NONROOT=true
ENV NVIDIA_VISIBLE_DEVICES=all \
NVIDIA_DRIVER_CAPABILITIES=compute,utility \
ROCR_VISIBLE_DEVICES=all \
Expand All @@ -91,7 +87,10 @@ ENV NVIDIA_VISIBLE_DEVICES=all \


WORKDIR /app
RUN mkdir -p /app/data /builds

USER 0:0
RUN mkdir -p /app/data /builds \
&& chown -R 65532:65532 /app /builds
COPY --from=backend-builder /build/arcane .
EXPOSE 3552
VOLUME ["/app/data"]
Expand Down
12 changes: 3 additions & 9 deletions docker/Dockerfile-agent
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# syntax=docker/dockerfile:1
ARG VERSION="dev"
ARG REVISION="unknown"
ARG ARCANE_RUNTIME_BASE_IMAGE=ghcr.io/getarcaneapp/base:trixie

FROM --platform=$BUILDPLATFORM golang:1.26-trixie AS agent-builder
ARG TARGETARCH
Expand Down Expand Up @@ -42,17 +43,10 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
-o /out/arcane-agent \
./cmd/main.go

FROM debian:trixie-slim AS agent
ARG TARGETARCH
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl tzdata tar gzip \
vainfo clinfo libdrm2 libsystemd0 \
&& if [ "$TARGETARCH" = "amd64" ]; then \
apt-get install -y --no-install-recommends intel-gpu-tools; \
fi \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
FROM ${ARCANE_RUNTIME_BASE_IMAGE} AS agent

WORKDIR /app
USER 0:0
RUN mkdir -p /app/data

COPY --from=agent-builder /out/arcane-agent ./arcane-agent
Expand Down
66 changes: 66 additions & 0 deletions docker/Dockerfile-dhi-base
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# syntax=docker/dockerfile:1

ARG DHI_BASE_IMAGE=dhi.io/debian-base:trixie

# Runtime deps are assembled in an apt-enabled stage, then copied into the
# DHI base so downstream Arcane builds do not need dhi.io credentials.
FROM debian:trixie-slim AS runtime-deps
ARG TARGETARCH

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
clinfo \
curl \
gzip \
libdrm2 \
libsystemd0 \
tar \
tzdata \
vainfo \
&& if [ "${TARGETARCH}" = "amd64" ]; then \
apt-get install -y --no-install-recommends intel-gpu-tools; \
fi \
&& mkdir -p /runtime-root \
&& copy_path() { \
path="$1"; \
if [ -e "${path}" ]; then \
mkdir -p "/runtime-root$(dirname "${path}")"; \
cp -aL "${path}" "/runtime-root${path}"; \
fi; \
} \
&& copy_resolved_path() { \
path="$1"; \
if [ -e "${path}" ]; then \
resolved_path="$(readlink -f "${path}")"; \
mkdir -p "/runtime-root$(dirname "${resolved_path}")"; \
cp -aL "${path}" "/runtime-root${resolved_path}"; \
fi; \
} \
&& copy_tree() { \
path="$1"; \
if [ -e "${path}" ]; then \
mkdir -p "/runtime-root$(dirname "${path}")"; \
cp -aL "${path}" "/runtime-root${path}"; \
fi; \
} \
&& runtime_bins="/usr/bin/curl /usr/bin/tar /usr/bin/gzip /usr/bin/vainfo /usr/bin/clinfo" \
&& if [ "${TARGETARCH}" = "amd64" ]; then \
runtime_bins="${runtime_bins} $(find /usr/bin -maxdepth 1 -type f -name 'intel_*' | sort)"; \
fi \
&& for bin in ${runtime_bins}; do \
copy_path "${bin}"; \
ldd "${bin}" \
| awk '/=> \// { print $3 } /^\// { print $1 }' \
| sort -u \
| while read -r lib; do copy_resolved_path "${lib}"; done; \
done \
&& copy_tree /etc/ssl/certs \
&& copy_tree /usr/share/ca-certificates \
&& copy_tree /usr/share/zoneinfo \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

FROM ${DHI_BASE_IMAGE}

COPY --from=runtime-deps /runtime-root/ /
6 changes: 4 additions & 2 deletions docker/examples/compose.basic.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ services:
healthcheck:
test:
[
"CMD-SHELL",
"curl -fsS http://localhost:3552/api/health >/dev/null || exit 1",
"CMD",
"curl",
"-fsS",
"http://localhost:3552/api/health",
]
interval: 10s
timeout: 3s
Expand Down
6 changes: 4 additions & 2 deletions docker/examples/compose.proxy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ services:
healthcheck:
test:
[
"CMD-SHELL",
"curl -fsS http://localhost:3552/api/health >/dev/null || exit 1",
"CMD",
"curl",
"-fsS",
"http://localhost:3552/api/health",
]
interval: 10s
timeout: 3s
Expand Down
3 changes: 2 additions & 1 deletion tests/setup/compose-postgres.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ services:

arcane:
image: arcane:playwright-tests
user: root
ports:
- '3001:3552'
environment:
- ENVIRONMENT=testing
- APP_ENV=test
- PUID=65532
- PGID=65532
- DATABASE_URL=postgres://arcane:arcane_test_password@postgres:5432/arcane_test?sslmode=disable
- ENCRYPTION_KEY=3JDIgolks2tJ9ymm1AdqzlYMWu0DUWyt
- JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
Expand Down
3 changes: 2 additions & 1 deletion tests/setup/compose-proxy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ services:

arcane:
image: arcane:playwright-tests
user: root
ports:
- '3002:3552'
environment:
- ENVIRONMENT=testing
- APP_ENV=test
- PUID=65532
- PGID=65532
- ENCRYPTION_KEY=3JDIgolks2tJ9ymm1AdqzlYMWu0DUWyt
- JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
- ADMIN_STATIC_API_KEY=${E2E_ADMIN_STATIC_API_KEY:-}
Expand Down
3 changes: 2 additions & 1 deletion tests/setup/compose.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
services:
arcane:
image: arcane:playwright-tests
user: root
ports:
- '3000:3552'
environment:
- ENVIRONMENT=testing
- APP_ENV=test
- PUID=65532
- PGID=65532
- ENCRYPTION_KEY=3JDIgolks2tJ9ymm1AdqzlYMWu0DUWyt
- JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
- ADMIN_STATIC_API_KEY=${E2E_ADMIN_STATIC_API_KEY:-}
Expand Down
Loading