diff --git a/Dockerfile b/Dockerfile
index a413172..95a62be 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,6 +5,10 @@
# 변경한 사람: 이소은
# tensorflow version 2.13.0-gpu -> 2.18.0-gpu
+##### decs:260501#####
+# 변경한 사람: 임준영
+# Xfce, TigerVNC, noVNC 기반 GUI 접속 지원 추가
+
########################### version history end ##########################
# TensorFlow 2.18.0 GPU 공식 이미지 사용 (CUDA 12.3, cuDNN 8.9 포함, Ubuntu 22.04 기반)
@@ -31,6 +35,13 @@ vim \
wget \
curl \
ssh \
+dbus-x11 \
+xfce4 \
+xfce4-terminal \
+tigervnc-standalone-server \
+tigervnc-common \
+novnc \
+websockify \
software-properties-common
# motd install
@@ -79,6 +90,9 @@ RUN mkdir /jupyter_config \
# entrypoint.sh 복사
COPY entrypoint.sh /
+# noVNC 접속 포트. VNC 서버(5901)는 localhost에만 바인딩합니다.
+EXPOSE 6080
+
# SSHD 서버를 실행하고, entrypoint 파일을 start/restart 시 마다 실행, dev/null에 entrypoint 로그를 저장
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["bash", "/entrypoint.sh"]
diff --git a/README.md b/README.md
index af75160..95b1ad5 100644
--- a/README.md
+++ b/README.md
@@ -3,11 +3,18 @@
| 이미지 태그 (Image Tag) | TensorFlow 버전 | CUDA / cuDNN | 베이스 OS (Base OS) | 주요 변경사항 및 설명 |
| :--- | :--- | :--- | :--- | :--- |
-| `dguailab/decs:latest`
`dguailab/decs:260403` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | **(최신)** 계정/권한 검증 강화, Jupyter 비-root 실행, 의도되지 않은 MOTD 출력 방지 |
-| `dguailab/decs:260201` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | **(이전 수정 버전)** 의도되지 않은 MOTD 출력 방지 버그 수정 |
-| `dguailab/decs:251023` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | **(이전 안정 버전)** Jupyter Notebook 버전 변경으로 인한 오류 해결 버전 |
-| `dguailab/decs:251002` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | TensorFlow 2.18.0 업그레이드, 최신 GPU 환경 지원 |
-| `dguailab/decs:250926` | **2.13.0** | CUDA 11.8
cuDNN 8.6 | Ubuntu 20.04 | **(이전 안정 버전)** TensorFlow 2.13.0 기반의 안정화 버전 |
+| `dguailab/decs:latest`
`dguailab/decs:260501` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | **(최신)** Xfce + TigerVNC + noVNC 기반 브라우저 GUI 접속 지원 추가 |
+| `dguailab/decs:260427` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | 기존 사용자 컨테이너 변경 시 `~/.bashrc`의 conda initialize 블록 갱신, 경고 주석 영문화 및 인덴트 수정 |
+| `dguailab/decs:260403` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | 계정/권한 검증 강화, config-server 주입 기반 sudo 권한 축소, Jupyter 및 컨테이너 유지 프로세스 비-root 실행, gosu 추가 |
+| `dguailab/decs:260201` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | 의도되지 않은 MOTD 출력 방지 버그 수정 |
+| `dguailab/decs:251023` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | **(이전 안정 버전)** Jupyter Notebook 설정 파일 생성/경로 오류 해결, 랜덤 토큰 저장 방식 반영 |
+| `dguailab/decs:251002` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | TensorFlow 2.18.0 업그레이드, 최신 GPU 환경 지원, Docker Hub 자동 빌드/푸시 워크플로 도입 |
+| `dguailab/decs:250926` | **2.13.0** | CUDA 11.8
cuDNN 8.6 | Ubuntu 20.04 | **(이전 안정 버전)** 사용자 홈 디렉토리 소유권 설정 순서 수정, 이슈/PR 템플릿 추가 |
+| `dguailab/decs:250428` | **2.13.0** | CUDA 11.8
cuDNN 8.6 | Ubuntu 20.04 | Jupyter Lab 설정 파일을 사용자 홈의 `.jupyter` 경로에서 생성하고 사용하도록 수정 |
+| `dguailab/decs:250416` | **2.13.0** | CUDA 11.8
cuDNN 8.6 | Ubuntu 20.04 | Jupyter Notebook 설정 파일 생성 로직 수정, 10자리 랜덤 토큰 생성 및 파일 저장 추가 |
+| `dguailab/decs:250323` | **2.13.0** | CUDA 11.8
cuDNN 8.6 | Ubuntu 20.04 | `useradd` 옵션 보완, MOTD 공지 추가, TensorFlow ASCII 출력 제거 |
+| `dguailab/decs:250309` | **2.13.0** | CUDA 11.8
cuDNN 8.6 | Ubuntu 20.04 | ldconfig permission denied 오류 방지, MOTD/Slack 공지 반영, Conda 최신화 반영 |
+| `dguailab/decs:250218` | **2.13.0** | CUDA 11.8
cuDNN 8.6 | Ubuntu 20.04 | Anaconda3 2024.10-1 설치로 Conda 24.9.2 업그레이드 |
## ⚙️ 사용 방법
필요한 버전의 이미지를 Docker Hub에서 pull 받아 사용합니다.
@@ -29,6 +36,37 @@ docker pull dguailab/decs:251002
docker pull dguailab/decs:250926
```
+### GUI 접속(noVNC)
+`260501` 버전부터 브라우저 기반 GUI 접속을 지원합니다. GUI 접속은 기본 비활성화 상태이며, 컨테이너 생성 시 `ENABLE_VNC=true` 환경변수를 전달한 경우에만 시작됩니다.
+
+- 컨테이너 내부 noVNC 포트: `6080`
+- 컨테이너 내부 VNC 포트: `localhost:5901` 전용
+- 접속 URL: `http://서버주소:외부포트/vnc.html`
+- VNC 비밀번호 저장 위치: `/home/$USER_ID/decs_jupyter_lab/vnc_password.txt`
+
+직접 `docker run`을 사용할 때는 다음처럼 환경변수와 포트를 함께 지정합니다.
+
+```
+-e ENABLE_VNC=true -p 외부포트:6080
+```
+
+운영 스크립트(`~/uid`)에서는 `--enable_vnc true`를 주면 컨테이너 내부 `6080` 포트를 자동으로 추가하고, Docker 실행 환경변수에 `ENABLE_VNC=true`를 함께 전달합니다. FARM2 예시는 외부 포트 범위 `9100~9199` 중 하나가 `6080`에 연결되는 방식입니다.
+
+```
+--enable_vnc true
+```
+
+관련 환경변수:
+
+| 환경변수 | 기본값 | 설명 |
+| :--- | :--- | :--- |
+| `ENABLE_VNC` | `false` | `true`로 설정한 경우에만 VNC/noVNC를 시작합니다. |
+| `VNC_PASSWORD` | 랜덤 8자리 | 지정하지 않으면 자동 생성 후 `vnc_password.txt`에 저장합니다. |
+| `VNC_RESOLUTION` | `1920x1080` | VNC 화면 해상도입니다. |
+| `VNC_DEPTH` | `24` | VNC 색상 깊이입니다. |
+| `VNC_DISPLAY` | `1` | VNC display 번호입니다. 기본 VNC 포트는 `5901`입니다. |
+| `NOVNC_PORT` | `6080` | noVNC가 컨테이너 내부에서 listen할 포트입니다. |
+
# ⚒️ 빌드 자동화
이 Docker 이미지는 GitHub Actions를 통해 자동으로 빌드 및 배포됩니다.
diff --git a/entrypoint.sh b/entrypoint.sh
index 77b3d5c..b7b9df3 100644
--- a/entrypoint.sh
+++ b/entrypoint.sh
@@ -66,6 +66,97 @@ ensure_sshd_allow_user() {
fi
}
+start_novnc() {
+ case "${ENABLE_VNC:-false}" in
+ true|TRUE|1|yes|YES|on|ON) ;;
+ *)
+ echo "VNC/noVNC disabled. Set ENABLE_VNC=true to enable it."
+ return 0
+ ;;
+ esac
+
+ local vnc_dir="$USER_HOME/.vnc"
+ local vnc_display="${VNC_DISPLAY:-1}"
+ local vnc_resolution="${VNC_RESOLUTION:-1920x1080}"
+ local vnc_depth="${VNC_DEPTH:-24}"
+ local novnc_port="${NOVNC_PORT:-6080}"
+ local vnc_password_file="$JUPYTER_DIR/vnc_password.txt"
+ local vnc_password
+
+ vnc_display="${vnc_display#:}"
+ if ! [[ "$vnc_display" =~ ^[0-9]+$ && "$novnc_port" =~ ^[0-9]+$ && "$vnc_depth" =~ ^[0-9]+$ ]]; then
+ echo "Invalid VNC configuration. Check VNC_DISPLAY, NOVNC_PORT, and VNC_DEPTH."
+ return 1
+ fi
+ local vnc_port=$((5900 + vnc_display))
+
+ if ! command -v vncserver >/dev/null 2>&1 || ! command -v websockify >/dev/null 2>&1; then
+ echo "VNC/noVNC packages are not installed. Skipping GUI startup."
+ return 0
+ fi
+
+ mkdir -p "$vnc_dir" "$JUPYTER_DIR" /tmp/.X11-unix /tmp/.ICE-unix
+ chown root:root /tmp/.X11-unix /tmp/.ICE-unix
+ chmod 1777 /tmp/.X11-unix /tmp/.ICE-unix
+
+ if [[ -n "${VNC_PASSWORD:-}" ]]; then
+ vnc_password="$VNC_PASSWORD"
+ elif [[ -s "$vnc_password_file" ]]; then
+ # head가 먼저 종료되며 tr이 SIGPIPE를 받아 pipefail로 실패 판정되는 것을 막는다.
+ vnc_password=$(tr -d '\r\n' < "$vnc_password_file" | head -c 8 || true)
+ else
+ vnc_password=$(head -c 64 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 8 || true)
+ fi
+ vnc_password="${vnc_password:0:8}"
+
+ if [[ -z "$vnc_password" ]]; then
+ echo "Failed to prepare VNC password. Skipping GUI startup."
+ return 1
+ fi
+
+ printf "%s\n" "$vnc_password" > "$vnc_password_file"
+ chmod 600 "$vnc_password_file"
+
+ printf "%s\n" "$vnc_password" | vncpasswd -f > "$vnc_dir/passwd"
+ chmod 600 "$vnc_dir/passwd"
+
+ cat > "$vnc_dir/xstartup" <<'EOF'
+#!/bin/sh
+unset SESSION_MANAGER
+unset DBUS_SESSION_BUS_ADDRESS
+export XDG_SESSION_TYPE=x11
+export XKL_XMODMAP_DISABLE=1
+xrdb "$HOME/.Xresources" 2>/dev/null || true
+exec dbus-launch --exit-with-session startxfce4
+EOF
+ chmod +x "$vnc_dir/xstartup"
+ chown -R "$TARGET_UID:$TARGET_GID" "$vnc_dir" "$vnc_password_file"
+
+ gosu "$USER_ID:$USER_GROUP" env HOME="$USER_HOME" USER="$USER_ID" \
+ vncserver -kill ":$vnc_display" >/tmp/vnc-kill.log 2>&1 || true
+
+ echo "trying TigerVNC on localhost:$vnc_port..."
+ if ! gosu "$USER_ID:$USER_GROUP" env HOME="$USER_HOME" USER="$USER_ID" \
+ vncserver -localhost yes ":$vnc_display" -geometry "$vnc_resolution" -depth "$vnc_depth" >/tmp/vncserver.log 2>&1; then
+ echo "TigerVNC startup failed. See /tmp/vncserver.log."
+ cat /tmp/vncserver.log
+ return 1
+ fi
+ echo "TigerVNC listening on localhost:$vnc_port"
+
+ pkill -f "websockify.*$novnc_port" >/dev/null 2>&1 || true
+ # /tmp/novnc.log를 사용자 소유로 미리 만들어 강등된 권한에서도 append 가능하게 한다.
+ : > /tmp/novnc.log
+ chown "$TARGET_UID:$TARGET_GID" /tmp/novnc.log
+ chmod 640 /tmp/novnc.log
+ echo "trying noVNC on 0.0.0.0:$novnc_port..."
+ # 비특권 포트(6080)이므로 root 권한이 불필요하다. 사용자 권한으로 강등 실행.
+ nohup gosu "$USER_ID:$USER_GROUP" env HOME="$USER_HOME" USER="$USER_ID" \
+ websockify --web=/usr/share/novnc "0.0.0.0:$novnc_port" "localhost:$vnc_port" \
+ >/tmp/novnc.log 2>&1 &
+ echo "noVNC listening on port $novnc_port. VNC password saved to $vnc_password_file"
+}
+
apt-get update
apt-get install -y auditd
@@ -83,6 +174,17 @@ mkdir -p "$USER_HOME"
chown "$TARGET_UID:$TARGET_GID" "$USER_HOME"
chmod 750 "$USER_HOME"
+# 사용자 홈에 dotfile이 없으면 /etc/skel에서 부트스트랩한다.
+# 로그인 셸이 ~/.profile → ~/.bashrc 체인으로 PS1을 정상화하도록 보장한다.
+for skel_file in .profile .bashrc .bash_logout; do
+ src="/etc/skel/$skel_file"
+ dst="$USER_HOME/$skel_file"
+ if [[ -f "$src" && ! -e "$dst" ]]; then
+ install -m 644 -o "$TARGET_UID" -g "$TARGET_GID" "$src" "$dst"
+ fi
+done
+unset skel_file src dst
+
# MOTD 공지 출력하도록 설정
sed -i 's/^#\?UsePAM .*/UsePAM yes/' /etc/ssh/sshd_config
ensure_sshd_allow_user "svmanager"
@@ -112,6 +214,11 @@ for file in /etc/update-motd.d/60-unminimize /etc/update-motd.d/10-help-text; do
done
sed -i.bak '/echo -e "\\e\[1;31m"/d; /cat< /etc/bash.bashrc 경로로 tf-docker 프롬프트가
+# 노출되는 현상을 방지한다.
+sed -i.bak '/tf-docker/d; /^cd "\$HOME"$/d' /etc/bash.bashrc
+
if [[ -f "/etc/legal" ]]; then
rm /etc/legal
fi
@@ -134,6 +241,9 @@ fi
sed -i "1i c.JupyterApp.config_file_name = 'jupyter_notebook_config.py'\nc.NotebookApp.allow_origin = '*'\nc.NotebookApp.ip = '0.0.0.0'\nc.NotebookApp.open_browser = False\nc.NotebookApp.allow_remote_access = True\nc.NotebookApp.allow_root = False\nc.NotebookApp.notebook_dir='$JUPYTER_DIR'" "$JUPYTER_CONFIG_FILE"
chown "$TARGET_UID:$TARGET_GID" "$JUPYTER_CONFIG_FILE"
+# noVNC 기동. 외부에서는 컨테이너의 6080 포트를 매핑해서 접속합니다.
+start_novnc || echo "VNC/noVNC startup failed."
+
# ldconfig permission 오류 방지
# bash.bashrc에서 ldconfig 명령어 삭제 후 명령어 실행 및 결과 출력
sed -i '/ldconfig/d' /etc/bash.bashrc
@@ -141,7 +251,7 @@ ldconfig && echo "ldconfig executed successfully" || echo "ldconfig failed"
# Jupyter 실행과 컨테이너 유지는 비-root 사용자로 전환한다.
exec gosu "$USER_ID:$USER_GROUP" bash -lc '
-TOKEN=$(tr -dc A-Za-z0-9 "'"$JUPYTER_DIR"'/jupyter_token.txt"
chmod 600 "'"$JUPYTER_DIR"'/jupyter_token.txt"
echo "trying jupyter lab..."