feat: build docker images with nix

This commit is contained in:
Keisuke Hirata 2026-07-22 18:43:35 +09:00
parent a8236ff3b4
commit c1ee1ff9e9
No known key found for this signature in database
6 changed files with 329 additions and 146 deletions

View File

@ -1,12 +0,0 @@
target/
.git/
.yoi/
.direnv/
result
result-*
.env
.env.*
**/node_modules/
web/workspace/build/
web/workspace/.svelte-kit/
web/workspace/.vite/

View File

@ -1,88 +0,0 @@
# syntax=docker/dockerfile:1.7
ARG RUST_VERSION=1.95.0
ARG DEBIAN_VERSION=bookworm
ARG DENO_VERSION=2.7.14
ARG NGINX_VERSION=1.27-alpine
FROM rust:${RUST_VERSION}-${DEBIAN_VERSION} AS rust-source
WORKDIR /src
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
git \
libssl-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
COPY Cargo.toml Cargo.lock ./
COPY crates ./crates
COPY resources ./resources
COPY tests ./tests
FROM rust-source AS runtime-builder
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/src/target \
cargo build --release -p worker-runtime --bin worker-runtime-rest-server --features ws-server,fs-store \
&& mkdir -p /out \
&& cp /src/target/release/worker-runtime-rest-server /out/worker-runtime-rest-server
FROM rust-source AS server-builder
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/src/target \
cargo build --release -p yoi-workspace-server --bin yoi-workspace-server \
&& mkdir -p /out \
&& cp /src/target/release/yoi-workspace-server /out/yoi-workspace-server
FROM denoland/deno:${DENO_VERSION} AS web-builder
WORKDIR /src/web/workspace
COPY web/workspace ./
RUN deno task build
FROM debian:${DEBIAN_VERSION}-slim AS runtime
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
bash \
ca-certificates \
git \
libssl3 \
openssh-client \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --system --uid 10001 --gid root --home-dir /runtime-data --shell /usr/sbin/nologin yoi \
&& mkdir -p /runtime-data /workdirs \
&& chown -R yoi:root /runtime-data /workdirs
COPY --from=runtime-builder /out/worker-runtime-rest-server /usr/local/bin/worker-runtime-rest-server
USER yoi
EXPOSE 38800
VOLUME ["/runtime-data", "/workdirs"]
ENTRYPOINT ["worker-runtime-rest-server"]
CMD ["--bind", "0.0.0.0:38800", "--display-name", "Docker Runtime", "--fs-root", "/runtime-data", "--workdir-target", "/workdirs"]
FROM debian:${DEBIAN_VERSION}-slim AS server
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
git \
libssl3 \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --system --uid 10002 --gid root --home-dir /server-data --shell /usr/sbin/nologin yoi \
&& mkdir -p /server-data /workspace \
&& chown -R yoi:root /server-data /workspace
COPY --from=server-builder /out/yoi-workspace-server /usr/local/bin/yoi-workspace-server
USER yoi
EXPOSE 8787
VOLUME ["/server-data", "/workspace"]
ENTRYPOINT ["yoi-workspace-server"]
CMD ["serve", "--workspace", "/workspace", "--db", "/server-data/workspace.db", "--listen", "0.0.0.0:8787"]
FROM nginx:${NGINX_VERSION} AS webui
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=web-builder /src/web/workspace/build /usr/share/nginx/html
EXPOSE 80

304
docker.nix Normal file
View File

@ -0,0 +1,304 @@
{
pkgs,
yoi ? pkgs.callPackage ./package.nix { },
}:
let
lib = pkgs.lib;
imageTag = "latest";
mkRoot =
name: paths: _extraPathsToLink:
pkgs.symlinkJoin {
inherit name paths;
};
runtimeDirs = pkgs.runCommand "yoi-runtime-dirs" { } ''
mkdir -p "$out/runtime-data" "$out/workdirs"
'';
serverDirs = pkgs.runCommand "yoi-server-dirs" { } ''
mkdir -p "$out/server-data" "$out/workspace"
'';
runtimeRoot =
mkRoot "yoi-runtime-root"
[
yoi
pkgs.bashInteractive
pkgs.coreutils
pkgs.cacert
pkgs.git
pkgs.openssh
runtimeDirs
]
[
"/runtime-data"
"/workdirs"
];
serverRoot =
mkRoot "yoi-server-root"
[
yoi
pkgs.coreutils
pkgs.cacert
pkgs.git
serverDirs
]
[
"/server-data"
"/workspace"
];
webuiSrc = lib.cleanSourceWith {
src = ./web/workspace;
filter =
path: type:
let
baseName = baseNameOf path;
in
!(baseName == "node_modules" || baseName == ".svelte-kit" || baseName == "build");
};
webuiDeps = pkgs.stdenvNoCC.mkDerivation {
pname = "yoi-webui-deno-deps";
version = "0.1.0";
src = webuiSrc;
nativeBuildInputs = [ pkgs.deno ];
outputHashAlgo = "sha256";
outputHashMode = "recursive";
outputHash = "sha256-q+otr+ANR9gB8bRZKFZDfNpM6rnQ4B4wNd/s1QNeSA4=";
buildPhase = ''
runHook preBuild
export HOME="$TMPDIR/home"
export DENO_DIR="$TMPDIR/deno-cache"
mkdir -p "$HOME" "$DENO_DIR"
deno task build
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p "$out"
cp -R "$DENO_DIR" "$out/deno-cache"
if [ -d node_modules ]; then
cp -R node_modules "$out/node_modules"
fi
runHook postInstall
'';
};
webuiStatic = pkgs.stdenvNoCC.mkDerivation {
pname = "yoi-webui-static";
version = "0.1.0";
src = webuiSrc;
nativeBuildInputs = [ pkgs.deno ];
buildPhase = ''
runHook preBuild
export HOME="$TMPDIR/home"
export DENO_DIR="$TMPDIR/deno-cache"
mkdir -p "$HOME"
cp -R ${webuiDeps}/deno-cache "$DENO_DIR"
chmod -R u+w "$DENO_DIR"
if [ -d ${webuiDeps}/node_modules ]; then
cp -R ${webuiDeps}/node_modules node_modules
chmod -R u+w node_modules
fi
deno task build
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p "$out"
cp -R build/. "$out/"
runHook postInstall
'';
};
webuiRoot = pkgs.runCommand "yoi-webui-root" { } ''
mkdir -p "$out/usr/share/yoi-webui"
cp -R ${webuiStatic}/. "$out/usr/share/yoi-webui/"
'';
webuiDirs = pkgs.runCommand "yoi-webui-dirs" { } ''
mkdir -p "$out/etc" "$out/tmp" "$out/var/cache/nginx" "$out/var/log/nginx"
touch "$out/tmp/.keep" "$out/var/cache/nginx/.keep" "$out/var/log/nginx/.keep"
cat > "$out/etc/passwd" <<'EOF'
root:x:0:0:root:/root:/bin/sh
nobody:x:65534:65534:nobody:/var/empty:/sbin/nologin
EOF
cat > "$out/etc/group" <<'EOF'
root:x:0:
nobody:x:65534:
nogroup:x:65534:
EOF
'';
nginxConf = pkgs.writeText "yoi-webui-nginx.conf" ''
pid /tmp/nginx.pid;
error_log /dev/stderr info;
events {}
http {
include ${pkgs.nginx}/conf/mime.types;
access_log /dev/stdout;
map $http_upgrade $connection_upgrade {
default upgrade;
''' close;
}
server {
listen 80;
server_name _;
root /usr/share/yoi-webui;
index index.html;
resolver 127.0.0.11 valid=30s ipv6=off;
set $yoi_backend server:8787;
location = /api {
proxy_pass http://$yoi_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location /api/ {
proxy_pass http://$yoi_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location / {
try_files $uri $uri/ /index.html;
}
}
}
'';
webuiImageRoot =
mkRoot "yoi-webui-root-env"
[
pkgs.nginx
pkgs.cacert
webuiRoot
webuiDirs
]
[
"/usr/share/yoi-webui"
"/var"
"/tmp"
];
commonEnv = [
"SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
"GIT_SSL_CAINFO=/etc/ssl/certs/ca-bundle.crt"
];
in
{
runtime = pkgs.dockerTools.buildImage {
name = "yoi-runtime";
tag = imageTag;
copyToRoot = runtimeRoot;
config = {
Entrypoint = [ "/bin/worker-runtime-rest-server" ];
Cmd = [
"--bind"
"0.0.0.0:38800"
"--display-name"
"Docker Runtime"
"--fs-root"
"/runtime-data"
"--workdir-target"
"/workdirs"
];
Env = [ "PATH=/bin" ] ++ commonEnv;
ExposedPorts = {
"38800/tcp" = { };
};
Volumes = {
"/runtime-data" = { };
"/workdirs" = { };
};
User = "10001:0";
WorkingDir = "/runtime-data";
};
};
server = pkgs.dockerTools.buildImage {
name = "yoi-server";
tag = imageTag;
copyToRoot = serverRoot;
config = {
Entrypoint = [ "/bin/yoi-workspace-server" ];
Cmd = [
"serve"
"--workspace"
"/workspace"
"--db"
"/server-data/workspace.db"
"--listen"
"0.0.0.0:8787"
];
Env = [ "PATH=/bin" ] ++ commonEnv;
ExposedPorts = {
"8787/tcp" = { };
};
Volumes = {
"/server-data" = { };
"/workspace" = { };
};
User = "10002:0";
WorkingDir = "/server-data";
};
};
webui = pkgs.dockerTools.buildImage {
name = "yoi-webui";
tag = imageTag;
copyToRoot = webuiImageRoot;
config = {
Entrypoint = [
"/bin/nginx"
"-c"
"${nginxConf}"
"-g"
"daemon off;"
];
Env = [ "PATH=/bin" ] ++ commonEnv;
ExposedPorts = {
"80/tcp" = { };
};
};
};
webui-static = webuiStatic;
}

View File

@ -1,41 +0,0 @@
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
resolver 127.0.0.11 valid=30s ipv6=off;
set $yoi_backend server:8787;
location = /api {
proxy_pass http://$yoi_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location /api/ {
proxy_pass http://$yoi_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location / {
try_files $uri $uri/ /index.html;
}
}

View File

@ -17,6 +17,13 @@
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
yoi = pkgs.callPackage ./package.nix { }; yoi = pkgs.callPackage ./package.nix { };
dockerImages =
if pkgs.stdenv.isLinux then
import ./docker.nix {
inherit pkgs yoi;
}
else
{ };
mkApp = name: description: { mkApp = name: description: {
type = "app"; type = "app";
program = "${yoi}/bin/${name}"; program = "${yoi}/bin/${name}";
@ -24,8 +31,16 @@
}; };
in in
{ {
packages.default = yoi; packages = {
packages.yoi = yoi; default = yoi;
yoi = yoi;
}
// pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
docker-runtime = dockerImages.runtime;
docker-server = dockerImages.server;
docker-webui = dockerImages.webui;
webui-static = dockerImages.webui-static;
};
apps.default = mkApp "yoi" "Run the Yoi terminal UI"; apps.default = mkApp "yoi" "Run the Yoi terminal UI";
apps.yoi = mkApp "yoi" "Run the Yoi terminal UI"; apps.yoi = mkApp "yoi" "Run the Yoi terminal UI";

View File

@ -43,7 +43,7 @@ rustPlatform.buildRustPackage rec {
filter = sourceFilter; filter = sourceFilter;
}; };
cargoHash = "sha256-MUEySmRu5Te8+GCwlt3qk7jxcumYIMvnuvj64o2XuDw="; cargoHash = "sha256-iZaTREhL/aLixn67A1+Gi9opqh2j/yyFuGMyBBvuATM=";
depsExtraArgs = { depsExtraArgs = {
# Older fetchCargoVendor utilities used crates.io's API download endpoint, # Older fetchCargoVendor utilities used crates.io's API download endpoint,
@ -93,6 +93,7 @@ rustPlatform.buildRustPackage rec {
postBuild = '' postBuild = ''
cargo build --offline --profile release -p yoi-workspace-server --bin yoi-workspace-server cargo build --offline --profile release -p yoi-workspace-server --bin yoi-workspace-server
cargo build --offline --profile release -p worker-runtime --bin worker-runtime-rest-server --features ws-server,fs-store
''; '';
# The package check is a credential-free install smoke check below. Running the # The package check is a credential-free install smoke check below. Running the
@ -105,13 +106,15 @@ rustPlatform.buildRustPackage rec {
yoi_bin=$(find . -type f -name yoi | head -n 1) yoi_bin=$(find . -type f -name yoi | head -n 1)
workspace_server_bin=$(find . -type f -name yoi-workspace-server | head -n 1) workspace_server_bin=$(find . -type f -name yoi-workspace-server | head -n 1)
if [ -z "$yoi_bin" ] || [ -z "$workspace_server_bin" ]; then worker_runtime_bin=$(find . -type f -name worker-runtime-rest-server | head -n 1)
if [ -z "$yoi_bin" ] || [ -z "$workspace_server_bin" ] || [ -z "$worker_runtime_bin" ]; then
echo "built binaries not found" >&2 echo "built binaries not found" >&2
find . -maxdepth 6 -type f \( -name yoi -o -name yoi-workspace-server \) -print >&2 find . -maxdepth 6 -type f \( -name yoi -o -name yoi-workspace-server -o -name worker-runtime-rest-server \) -print >&2
exit 1 exit 1
fi fi
install -Dm755 "$yoi_bin" "$out/bin/yoi" install -Dm755 "$yoi_bin" "$out/bin/yoi"
install -Dm755 "$workspace_server_bin" "$out/bin/yoi-workspace-server" install -Dm755 "$workspace_server_bin" "$out/bin/yoi-workspace-server"
install -Dm755 "$worker_runtime_bin" "$out/bin/worker-runtime-rest-server"
runHook postInstall runHook postInstall
''; '';
@ -123,7 +126,9 @@ rustPlatform.buildRustPackage rec {
"$out/bin/yoi" worker --help >/dev/null "$out/bin/yoi" worker --help >/dev/null
test -x "$out/bin/yoi" test -x "$out/bin/yoi"
test -x "$out/bin/yoi-workspace-server" test -x "$out/bin/yoi-workspace-server"
test -x "$out/bin/worker-runtime-rest-server"
"$out/bin/yoi-workspace-server" --help >/dev/null "$out/bin/yoi-workspace-server" --help >/dev/null
"$out/bin/worker-runtime-rest-server" --help >/dev/null
test ! -e "$out/bin/yoi-pod" test ! -e "$out/bin/yoi-pod"
test ! -e "$out/share/yoi/resources" test ! -e "$out/share/yoi/resources"
if "$out/bin/yoi" --session not-a-uuid 2>yoi.err; then if "$out/bin/yoi" --session not-a-uuid 2>yoi.err; then