Skip to content
Merged
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
57 changes: 57 additions & 0 deletions .github/workflows/bun-migration-docker-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ name: Bun Migration Docker Check

on:
workflow_dispatch:
inputs:
railpack-frontend:
description: "Railpack frontend for the sample app build, e.g. a version existing applications keep (empty: the image's Railpack version)"
type: string
default: ""
pull_request:
branches: [AgentHits-Dev]
paths:
- ".github/workflows/bun-migration-docker-check.yml"
- ".trivyignore.yaml"
- "Dockerfile"
- "Dockerfile.cloud"
- "Dockerfile.server"
Expand Down Expand Up @@ -58,6 +64,57 @@ jobs:
test -f /app/packages/server/dist/index.js
node --input-type=module -e "console.log(await import.meta.resolve(\"@dokploy/server\"))"
'
# The panel calls these at runtime; the image installs them without
# apt recommends and without the Docker engine.
docker run --rm --entrypoint sh dokploy-bun-check:main -ec '
docker --version
docker buildx version
docker compose version
rclone version | head -n 1
railpack --version
pack --version
nixpacks --version
git lfs version
ssh -V
for tool in ssh-keyscan pkill htpasswd rsync jq zip unzip ip tini; do command -v "$tool"; done
'

# Repeats the panel's Railpack path (railpack prepare, then buildx with the
# Railpack frontend on a docker-container builder) from inside the image,
# so a Railpack bump that cannot build a plain Node app fails here.
- name: Build a sample app with Railpack
env:
RAILPACK_FRONTEND: ${{ inputs.railpack-frontend }}
run: |
app="$RUNNER_TEMP/railpack-app"
mkdir -p "$app"
printf '%s\n' '{"name":"railpack-smoke","private":true,"scripts":{"start":"node index.js"}}' > "$app/package.json"
printf '%s\n' 'require("node:http").createServer((_, res) => res.end("railpack-ok")).listen(process.env.PORT || 3000);' > "$app/index.js"
frontend="${RAILPACK_FRONTEND:-$(docker run --rm --entrypoint railpack dokploy-bun-check:main --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1)}"
echo "Railpack frontend: $frontend"
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v "$app:/src" -e FRONTEND="$frontend" --entrypoint sh dokploy-bun-check:main -ec '
railpack prepare /src --plan-out /src/railpack-plan.json --info-out /src/railpack-info.json
docker buildx create --name railpack-smoke --driver docker-container
docker buildx build --builder railpack-smoke --build-arg "BUILDKIT_SYNTAX=ghcr.io/railwayapp/railpack-frontend:v$FRONTEND" -f /src/railpack-plan.json --output type=docker,name=railpack-smoke /src
docker buildx rm railpack-smoke
'
docker run -d --name railpack-smoke -e PORT=3000 -p 127.0.0.1:3999:3000 railpack-smoke
for _ in $(seq 1 30); do
if [ "$(curl -fs http://127.0.0.1:3999 || true)" = railpack-ok ]; then exit 0; fi
sleep 1
done
docker logs railpack-smoke
exit 1

# Same scan as image-audit.yml, on the image this PR builds.
- name: Scan main image
env:
TRIVY_IMAGE: aquasec/trivy:0.76.1@sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c
run: |
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v "$PWD:/src:ro" -w /src "$TRIVY_IMAGE" image \
--image-src docker --scanners vuln --severity CRITICAL,HIGH \
--ignore-unfixed --ignorefile .trivyignore.yaml --exit-code 1 \
--no-progress dokploy-bun-check:main

service-image-builds:
runs-on: ubuntu-24.04
Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/image-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Image audit

# bun audit sees only the npm dependencies. This scan reads the published panel
# image: its Debian packages, the Go binaries of the bundled tools (Docker CLI,
# rclone, Railpack, pack) and the npm packages inside it. It fails on critical
# and high findings that already have a fix; .trivyignore.yaml lists the ones
# still waiting for a tool release. GitHub notifies the person who last changed
# the cron line when a scheduled run fails.
on:
schedule:
- cron: "53 5 * * 1"
workflow_dispatch:
inputs:
image:
description: Image to scan
type: string
default: ghcr.io/agenthits/dokploy:agenthits-dev

permissions:
contents: read

env:
# Pinned by digest: in March 2026 Trivy releases, its images and the
# trivy-action tags were replaced with credential stealers (GHSA-69fq-xp46-6x23).
# bun-migration-docker-check.yml pins the same image.
TRIVY_IMAGE: aquasec/trivy:0.76.1@sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c

jobs:
scan:
name: image audit
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Scan image
env:
IMAGE: ${{ inputs.image || 'ghcr.io/agenthits/dokploy:agenthits-dev' }}
run: |
docker run --rm -v "$PWD:/src:ro" -w /src "$TRIVY_IMAGE" image \
--image-src remote --scanners vuln --severity CRITICAL,HIGH \
--ignore-unfixed --ignorefile .trivyignore.yaml --exit-code 1 \
--no-progress "$IMAGE"
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
- name: Install Railpack
if: matrix.job == 'test' && runner.environment == 'github-hosted'
run: |
export RAILPACK_VERSION=0.40.1
export RAILPACK_VERSION=0.42.0
curl -sSL https://railpack.com/install.sh | bash
echo "Railpack installed $RAILPACK_VERSION"

Expand Down
21 changes: 21 additions & 0 deletions .trivyignore.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Fixable findings in bundled tools whose latest release still carries them.
# Each entry expires, so the image audit fails again and the tool gets checked
# for a new release.
vulnerabilities:
- id: CVE-2026-111943
paths:
- usr/bin/rclone
- usr/local/bin/railpack
statement: rclone 1.75.2 and Railpack 0.42.0 are built with Go 1.26.9; the fix needs Go 1.26.10.
expired_at: 2026-11-27
- id: CVE-2026-78669
paths:
- usr/libexec/docker/cli-plugins/docker-buildx
- usr/libexec/docker/cli-plugins/docker-compose
statement: buildx 0.38.1 and compose 5.6.1, the latest releases, use golang.org/x/net below 0.60.0.
expired_at: 2026-11-27
- id: CVE-2026-17106
paths:
- usr/libexec/docker/cli-plugins/docker-buildx
statement: buildx 0.38.1, the latest release, uses moby/go-archive 0.2.0.
expired_at: 2026-11-27
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ One GitHub release `agenthits-vX.Y.Z` covers a batch of work, not a single PR (o
- The one-day age is `minimumReleaseAge` in `bunfig.toml` and `MIN_RELEASE_AGE` in the workflow; change both together.
- A commit pushed to the branch of the open PR stops its daily rebuild until the PR is merged or closed.
- `.github/workflows/dependency-audit.yml` runs `bun audit` every Monday for advisories published between updates.
- `bun audit` does not see the image. Trivy scans the published `agenthits-dev` image every Monday (`.github/workflows/image-audit.yml`) and the image a PR builds (`bun-migration-docker-check.yml`). Both fail on critical and high findings that have a fix in Debian packages, in the Go binaries of the bundled tools (Docker CLI, rclone, Railpack, pack) or in npm packages inside the image. `.trivyignore.yaml` holds findings whose latest tool release still carries them; each entry expires, so the audit fails again and the tool gets rechecked. Trivy is pinned by digest in both workflows; move them together.
38 changes: 31 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ COPY packages/server/package.json ./packages/server/package.json
# build-stage install kept Bun's isolated store (node_modules/.bun, 1.6 GB) in
# the image, and packages/server resolved its imports through that store, so
# the panel loaded every shared dependency into memory twice.
#
# The @trpc packages list typescript as a required peer, so the production
# install also brings the TypeScript 7 compiler, a Go binary the panel never runs.
FROM deps AS prod-deps
RUN --mount=type=cache,id=bun,target=/root/.bun/install/cache \
bun install --production --frozen-lockfile --linker hoisted \
&& rm -f node_modules/dokploy node_modules/@dokploy/api node_modules/@dokploy/schedules \
&& rm -rf node_modules/typescript node_modules/@typescript node_modules/.bin/tsc \
&& mkdir -p packages/server/node_modules

FROM deps AS build
Expand Down Expand Up @@ -61,7 +65,18 @@ ENV NODE_ENV=production
ENV DOKPLOY_OFFICIAL_VERSION=$DOKPLOY_OFFICIAL_VERSION
ENV DOKPLOY_FORK_VERSION=$DOKPLOY_FORK_VERSION

RUN apt-get update && apt-get install -y tini curl unzip zip apache2-utils iproute2 rsync jq git-lfs && git lfs install && rm -rf /var/lib/apt/lists/*
# Recommends would add systemd, dbus and X11 libraries; the packages the panel
# calls are listed instead: openssh-client for git over SSH and ssh-keyscan,
# procps for pkill when a deployment is cancelled.
RUN apt-get update \
&& apt-get install -y --no-install-recommends tini curl unzip zip apache2-utils iproute2 rsync jq git-lfs openssh-client procps xz-utils netbase \
&& git lfs install \
&& rm -rf /var/lib/apt/lists/*

# The panel runs on node and bun. npm, npx and node-gyp only serve the build
# stages, and npm ships its own copies of packages with open advisories.
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/node-gyp \
/usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/node-gyp

# Copy only the necessary files
COPY --from=build /usr/src/app/apps/dokploy/.next ./.next
Expand All @@ -78,12 +93,21 @@ COPY --from=build /usr/src/app/packages/server/package.json ./packages/server/pa
COPY --from=build /usr/src/app/packages/server/dist ./packages/server/dist


# Install docker
ARG DOCKER_VERSION=29.8.2
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh --version "$DOCKER_VERSION" && rm get-docker.sh
# The panel drives the host's Docker daemon through the mounted socket, so it
# needs only the CLI and its buildx and compose plugins. get.docker.com would
# also install the engine, containerd and the rootless extras: unused binaries
# that scanners report.
ARG DOCKER_VERSION=29.9.2
RUN . /etc/os-release \
&& install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends "docker-ce-cli=5:${DOCKER_VERSION}-1~debian.${VERSION_ID}~${VERSION_CODENAME}" docker-buildx-plugin docker-compose-plugin \
&& rm -rf /var/lib/apt/lists/*

# Install rclone
ARG RCLONE_VERSION=1.75.1
ARG RCLONE_VERSION=1.75.2
RUN apt-get update \
&& apt-get install -y --no-install-recommends unzip \
&& curl -fsSL "https://downloads.rclone.org/v${RCLONE_VERSION}/rclone-v${RCLONE_VERSION}-linux-amd64.zip" -o /tmp/rclone.zip \
Expand All @@ -102,12 +126,12 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
&& bun install -g tsx

# Install Railpack
ARG RAILPACK_VERSION=0.40.1
ARG RAILPACK_VERSION=0.42.0
RUN curl -sSL https://railpack.com/install.sh \
| RAILPACK_VERSION="$RAILPACK_VERSION" bash -s -- --yes --bin-dir /usr/local/bin

# Install buildpacks
COPY --from=buildpacksio/pack:0.40.9 /usr/local/bin/pack /usr/local/bin/pack
COPY --from=buildpacksio/pack:0.40.10 /usr/local/bin/pack /usr/local/bin/pack

EXPOSE 3000

Expand Down
10 changes: 5 additions & 5 deletions apps/dokploy/__test__/server/server-components.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ case "$1" in
if [ "$FAKE_CANDIDATE_AFTER_REPO" = 1 ] && [ ! -f "$DOKPLOY_APT_SOURCES_DIR/docker.list" ]; then
exit 0
fi
echo " docker-ce | 5:29.8.2-1~ubuntu.24.04~noble | https://download.docker.com/linux/ubuntu noble/stable amd64 Packages"
echo " docker-ce | 5:${PINNED_VERSIONS.docker}-1~ubuntu.24.04~noble | https://download.docker.com/linux/ubuntu noble/stable amd64 Packages"
;;
policy)
case "$2" in
Expand Down Expand Up @@ -726,7 +726,7 @@ const runComponentScript = (
CALL_LOG: callLog,
FAKE_STATE: dir,
FAKE_PREVIOUS_ENGINE: "28.3.0",
FAKE_TARGET_ENGINE: "29.8.2",
FAKE_TARGET_ENGINE: PINNED_VERSIONS.docker,
FAKE_OLD_IMAGE: "traefik:v3.6.25",
TMPDIR: dir,
TRAEFIK_SETTLE_SECONDS: "0",
Expand Down Expand Up @@ -763,7 +763,7 @@ const runComponentScript = (

const SPAWN_TEST_TIMEOUT_MS = 30_000;
const DOCKER_OLD = "5:28.3.0-1~ubuntu.24.04~noble";
const DOCKER_NEW = "5:29.8.2-1~ubuntu.24.04~noble";
const DOCKER_NEW = `5:${PINNED_VERSIONS.docker}-1~ubuntu.24.04~noble`;
const HTTP_CHALLENGE_CONFIG =
"certificatesResolvers:\n letsencrypt:\n acme:\n email: admin@example.com\n httpChallenge:\n entryPoint: web\n";
const TLS_CHALLENGE_CONFIG =
Expand Down Expand Up @@ -1413,7 +1413,7 @@ describe("buildComponentUpdateScript run order", { timeout: 60_000 }, () => {

expect(run.status).toBe(0);
expect(run.stdout).toContain(
"Docker not updated: Docker 29.8.2 is not offered by the Docker apt repository configured on this host.",
`Docker not updated: Docker ${PINNED_VERSIONS.docker} is not offered by the Docker apt repository configured on this host.`,
);
expect(run.stdout).toContain(
`Traefik version ${TRAEFIK_VERSION} installed`,
Expand Down Expand Up @@ -1499,7 +1499,7 @@ describe("buildComponentUpdateScript run order", { timeout: 60_000 }, () => {

expect(run.status).toBe(0);
expect(run.stdout).toContain(
"Docker not updated: Docker 29.8.2 is not in the apt sources, even after adding Docker's repository. The other components continue.",
`Docker not updated: Docker ${PINNED_VERSIONS.docker} is not in the apt sources, even after adding Docker's repository. The other components continue.`,
);
expect(run.stdout).toContain(
`Traefik version ${TRAEFIK_VERSION} installed`,
Expand Down
12 changes: 7 additions & 5 deletions apps/dokploy/components/dashboard/application/build/show.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { api } from "@/utils/api";

// Railpack versions from https://github.com/railwayapp/railpack/releases
export const RAILPACK_VERSIONS = [
"0.42.0",
"0.41.0",
"0.40.1",
"0.39.0",
"0.38.0",
Expand Down Expand Up @@ -142,7 +144,7 @@ const mySchema = z.discriminatedUnion("buildType", [
}),
z.object({
buildType: z.literal(BuildType.railpack),
railpackVersion: z.string().nullable().default("0.40.1"),
railpackVersion: z.string().nullable().default("0.42.0"),
}),
z.object({
buildType: z.literal(BuildType.static),
Expand Down Expand Up @@ -283,7 +285,7 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
: null,
railpackVersion:
data.buildType === BuildType.railpack
? data.railpackVersion || "0.40.1"
? data.railpackVersion || "0.42.0"
: null,
})
.then(async () => {
Expand Down Expand Up @@ -545,7 +547,7 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
size="sm"
onClick={() => {
setIsManualRailpackVersion(false);
field.onChange("0.40.1");
field.onChange("0.42.0");
}}
>
Use predefined versions
Expand All @@ -561,7 +563,7 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
field.onChange(value);
}
}}
value={field.value ?? "0.40.1"}
value={field.value ?? "0.42.0"}
>
<SelectTrigger>
<SelectValue placeholder="Select Railpack version" />
Expand All @@ -575,7 +577,7 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
{RAILPACK_VERSIONS.map((version) => (
<SelectItem key={version} value={version}>
v{version}
{version === "0.40.1" && (
{version === "0.42.0" && (
<Badge
variant="secondary"
className="ml-2 px-1 text-xs"
Expand Down
1 change: 1 addition & 0 deletions apps/dokploy/drizzle/0210_railpack_default_0_42_0.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "application" ALTER COLUMN "railpackVersion" SET DEFAULT '0.42.0';
Loading