Skip to content
This repository was archived by the owner on Dec 15, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
929a2bc
Changes workspace image to use kasm ubuntu image (#1)
prasadtalasila Nov 6, 2025
e337391
Now uses nginx for reverse proxy. Jupyter works thorugh reverse porxy…
DisasterlyDisco Nov 13, 2025
158b3c2
Updated Jupyter config to not be non-near-deprecated.
DisasterlyDisco Nov 27, 2025
32cefb2
Now only notifies code-server of port (instead of whole ip:port)
DisasterlyDisco Nov 27, 2025
606ce6a
Reverse proxy now works for all three services.
DisasterlyDisco Nov 27, 2025
f086e52
Updated compose, Dockerfile and README to represent the new reverse p…
DisasterlyDisco Nov 27, 2025
9739336
Jupyter now no longer opens a browser on image startup
DisasterlyDisco Nov 27, 2025
acc61cd
Consolidated port definitions into ENV variables set in the Dockerfil…
DisasterlyDisco Nov 27, 2025
729f7ad
VNC websocket adheres to subpath structure if informed of path as par…
DisasterlyDisco Nov 28, 2025
53ddaba
Merge branch 'workspace-nouveau' into 2-create-reverse-proxy-for-work…
DisasterlyDisco Dec 2, 2025
adf9f61
Removed websockify location in favor of http request parameter
DisasterlyDisco Dec 9, 2025
cdade78
Merge branch '2-create-reverse-proxy-for-workspace-image' of https://…
DisasterlyDisco Dec 9, 2025
de56be5
user is now set at container startup instead of image generation
DisasterlyDisco Dec 10, 2025
c4ddeb0
Drastic reordering of image structure. User is now setup on container…
DisasterlyDisco Dec 10, 2025
61551b5
DIsabled unneeded KASM services and returned to two-stage build
DisasterlyDisco Dec 10, 2025
04dc8bb
Switched away from being based on rolling-daily kasm image
DisasterlyDisco Dec 12, 2025
6cd3fcf
Updated headers and timeouts for vnc connection
DisasterlyDisco Dec 12, 2025
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
107 changes: 28 additions & 79 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
FROM kasmweb/core-ubuntu-noble:1.18.0 AS build-stage
FROM kasmweb/core-ubuntu-noble:1.18.0 AS configure
USER root

ARG MAIN_USER=dtaas-user

ENV HOME=/home/kasm-default-profile \
STARTUPDIR=/dockerstartup \
ENV CODE_SERVER_PORT=8054 \
HOME=/home/kasm-default-profile \
INST_DIR=${STARTUPDIR}/install \
JUPYTER_SERVER_PORT=8090 \
PERSISTENT_DIR=/workspace \
VNCOPTIONS="${VNCOPTIONS} -disableBasicAuth" \
MAIN_USER=${MAIN_USER}
KASM_SVC_AUDIO=0 \
KASM_SVC_AUDIO_INPUT=0 \
KASM_SVC_UPLOADS=0 \
KASM_SVC_GAMEPAD=0 \
KASM_SVC_WEBCAM=0 \
KASM_SVC_PRINTER=0 \
KASM_SVC_SMARTCARD=0

WORKDIR $HOME

COPY ./startup/ ${STARTUPDIR}
COPY ./install/ ${INST_DIR}
COPY ./config/kasm_vnc/kasmvnc.yaml /etc/kasmvnc/

RUN bash ${INST_DIR}/firefox/install_firefox.sh && \
bash ${INST_DIR}/nginx/install_nginx.sh && \
bash ${INST_DIR}/vscode/install_vscode_server.sh && \
bash ${INST_DIR}/jupyter/install_jupyter.sh
bash ${INST_DIR}/jupyter/install_jupyter.sh && \
bash ${INST_DIR}/dtaas_cleanup.sh

COPY ./startup/ ${STARTUPDIR}

COPY ./config/kasm_vnc/kasmvnc.yaml /etc/kasmvnc/
COPY ./config/jupyter/jupyter_notebook_config.py /etc/jupyter/

RUN chown 1000:0 ${HOME} && \
$STARTUPDIR/set_user_permission.sh ${HOME} && \
Expand All @@ -26,78 +37,16 @@ RUN chown 1000:0 ${HOME} && \
RUN mkdir ${PERSISTENT_DIR} && \
chmod a+rwx ${PERSISTENT_DIR}

ENV HOME=/home/${MAIN_USER}
RUN usermod --login ${MAIN_USER} --move-home --home ${HOME} kasm-user && \
groupmod --new-name ${MAIN_USER} kasm-user && \
adduser ${MAIN_USER} sudo && \
passwd -d ${MAIN_USER}

FROM scratch AS squashed-stage
COPY --from=build-stage / /
RUN adduser $(id -un 1000) sudo && \
passwd -d $(id -un 1000)

ARG CODE_SERVER_PORT=8080 \
DISTRO=ubuntu \
EXTRA_SH=noop.sh \
JUPYTER_LAB_PORT=8899 \
JUPYTER_NOTEBOOK_PORT=8888 \
LANG='en_US.UTF-8' \
LANGUAGE='en_US:en' \
LC_ALL='en_US.UTF-8' \
MAIN_USER=dtaas-user \
START_PULSEAUDIO=1 \
START_XFCE4=1 \
TZ='Etc/UTC'

ENV AUDIO_PORT=4901 \
CODE_SERVER_PORT=${CODE_SERVER_PORT} \
DEBIAN_FRONTEND=noninteractive \
DISPLAY=:1 \
DISTRO=$DISTRO \
GOMP_SPINCOUNT=0 \
HOME=/home/${MAIN_USER} \
INST_SCRIPTS=/dockerstartup/install \
JUPYTER_LAB_PORT=${JUPYTER_LAB_PORT} \
JUPYTER_NOTEBOOK_PORT=${JUPYTER_NOTEBOOK_PORT} \
KASMVNC_AUTO_RECOVER=true \
KASM_VNC_PATH=/usr/share/kasmvnc \
LANG=$LANG \
LANGUAGE=$LANGUAGE \
LC_ALL=$LC_ALL \
LD_LIBRARY_PATH=/opt/libjpeg-turbo/lib64/:/usr/local/lib/ \
LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:/usr/lib/i386-linux-gnu${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}:/usr/local/nvidia/lib:/usr/local/nvidia/lib64 \
MAIN_USER=${MAIN_USER} \
MAX_FRAME_RATE=24 \
NO_VNC_PORT=6901 \
OMP_WAIT_POLICY=PASSIVE \
PERSISTENT_DIR=/workspace \
PULSE_RUNTIME_PATH=/var/run/pulse \
SDL_GAMECONTROLLERCONFIG="030000005e040000be02000014010000,XInput Controller,platform:Linux,a:b0,b:b1,x:b2,y:b3,back:b8,guide:b16,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7" \
SHELL=/bin/bash \
START_PULSEAUDIO=$START_PULSEAUDIO \
STARTUPDIR=/dockerstartup \
START_XFCE4=$START_XFCE4 \
TERM=xterm \
VNC_COL_DEPTH=24 \
VNCOPTIONS="-PreferBandwidth -DynamicQualityMin=4 -DynamicQualityMax=7 -DLP_ClipDelay=0 -disableBasicAuth" \
VNC_PORT=5901 \
VNC_PORT=5901 \
VNC_PW=vncpassword \
VNC_RESOLUTION=1280x1024 \
VNC_RESOLUTION=1280x720 \
VNC_VIEW_ONLY_PW=vncviewonlypassword \
TZ=$TZ

EXPOSE $VNC_PORT \
$NO_VNC_PORT \
$UPLOAD_PORT \
$AUDIO_PORT \
$CODE_SERVER_PORT \
$JUPYTER_NOTEBOOK_PORT \
$JUPYTER_LAB_PORT
RUN python3 -c "import os, shlex; print('\n'.join(f'export {k}={shlex.quote(v)}' for k, v in os.environ.items()))" >> /tmp/.docker_set_envs && \
chmod 755 /tmp/.docker_set_envs

WORKDIR ${HOME}
FROM scratch AS deploy
COPY --from=configure / /

USER 1000
EXPOSE 8080

ENTRYPOINT ["/dockerstartup/kasm_default_profile.sh", "/dockerstartup/vnc_startup.sh", "/dockerstartup/kasm_startup.sh"]
ENTRYPOINT ["/dockerstartup/dtaas_shim.sh", "/dockerstartup/kasm_default_profile.sh", "/dockerstartup/vnc_startup.sh"]
CMD ["--wait"]
21 changes: 9 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,28 @@ sudo docker build -t workspace-nouveau:latest -f Dockerfile .

```ps1
sudo docker run -it --shm-size=512m \
-p 6901:6901 -p 8080:8080 -p 8888:8888 -p 8899:8899 \
-p 8080:8080\
workspace-nouveau:latest
```

## Use Services

An active container provides the following services

* ***Open workspace*** - https://localhost:6901
* ***Open VSCode*** - http://localhost:8080
* ***Open Jupyter Notebook*** - http://localhost:8888
* ***Open Jupyter Lab*** - http://localhost:8899
* ***Open workspace*** - http://localhost:8080/dtaas-user/tools/vnc?path=dtaas-user%2Ftools%2Fvnc%2Fwebsockify
* ***Open VSCode*** - http://localhost:8080/dtaas-user/tools/vscode
* ***Open Jupyter Notebook*** - http://localhost:8080
* ***Open Jupyter Lab*** - http://localhost:8080/dtaas-user/lab

## Current progress

* Based on the KASM core ubuntu image.
* Added VSCode service with [code-server](https://github.com/coder/code-server),
is started by the [custom_startup.sh](/startup/custom_startup.sh) script.
It is for now available on port 8080.
* Jupyter is available, Notebook on port 8888, Lab on 8899.
(All ports are subject to change)
* Jupyter is available.
* No longer need to authenticate when opening VNC Desktop.
* User is now a sudoer, can install debian packages, and user password
can be set at container instantiation (via the environmetn variable USER_PW).
* Still need to allow http (Only the virtual desktop itself
currently demands HTTPS).
* Still need to setup reverse proxy to redirect subpaths to tool ports.
can be set at container instantiation (via the environment variable USER_PW).
* All access to services is over http (VNC https is hidden behind reverse proxy).
* Reverse proxy exists, and VNC's websocket is forced to adchere to path structure with 'path' argument as path of http request.
* Still need to get image under 500 MB.
10 changes: 2 additions & 8 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,10 @@ services:
image: workspace-nouveau:latest
build:
context: .
args:
CODE_SERVER_PORT: 8080
JUPYTER_NOTEBOOK_PORT: 8888
JUPYTER_LAB_PORT: 8899
MAIN_USER: dtaas-user
environment:
- MAIN_USER=dtaas-user
ports:
- "6901:6901"
- "8080:8080"
- "8888:8888"
- "8899:8899"
shm_size: 512m
volumes:
- ./persistent_dir:/workspace
Expand Down
33 changes: 33 additions & 0 deletions config/jupyter/jupyter_notebook_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import os

c = get_config()

jupyter_server_port = int(os.getenv("JUPYTER_SERVER_PORT"))

# http connection config
c.ServerApp.ip = "0.0.0.0"
c.ServerApp.port = jupyter_server_port
c.ServerApp.allow_root = True
c.ServerApp.port_retries = 0
c.ServerApp.quit_button = False
c.ServerApp.allow_remote_access = True
c.ServerApp.disable_check_xsrf = True
c.ServerApp.allow_origin = "*"
c.ServerApp.trust_xheaders = True

# ensure that Jupyter doesn't open a browser window on image startup
c.NotebookApp.open_browser = False
c.LabApp.open_browser = False
c.ServerApp.open_browser = False
c.ExtensionApp.open_browser = False

# set base url if available
base_url = "/" + os.getenv("MAIN_USER", "")
if base_url is not None and base_url != "/":
c.ServerApp.base_url = base_url

# delete files fully when deleted
c.FileContentsManager.delete_to_trash = False

# deactivate token -> no authentication
c.IdentityProvider.token = ""
4 changes: 1 addition & 3 deletions config/kasm_vnc/kasmvnc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,4 @@ network:
udp:
public_ip: 127.0.0.1
runtime_configuration:
allow_override_standard_vnc_server_settings: true
allow_override_list:
- pointer.enabled
allow_override_standard_vnc_server_settings: true
16 changes: 16 additions & 0 deletions install/dtaas_cleanup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -e

rm -Rf ${STARTUPDIR}/audio_input
rm -Rf ${STARTUPDIR}/gamepad
rm -Rf ${STARTUPDIR}/jsmpeg
rm -Rf ${STARTUPDIR}/printer
rm -Rf ${STARTUPDIR}/recorder
rm -Rf ${STARTUPDIR}/smartcard
rm -Rf ${STARTUPDIR}/upload_server
rm -Rf ${STARTUPDIR}/webcam

apt-get clean
rm -rf /var/lib/apt/lists/*
rm -Rf /root/.cache/pip
rm -rf /tmp/*
4 changes: 1 addition & 3 deletions install/jupyter/install_jupyter.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ set -xe
apt-get update && apt-get install -y \
python3 \
python3-pip
apt-get clean
rm -rf /var/lib/apt/lists/*

pip install --break-system-packages \
pip install --break-system-packages --no-cache-dir \
jupyterlab \
notebook
16 changes: 16 additions & 0 deletions install/nginx/install_nginx.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -xe

apt-get install -y curl gnupg2 ca-certificates lsb-release ubuntu-keyring

curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
| tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \
| tee /etc/apt/sources.list.d/nginx.list

echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
| tee /etc/apt/preferences.d/99nginx

apt-get update && apt-get install -y nginx
1 change: 1 addition & 0 deletions persistent_dir/workspace
24 changes: 24 additions & 0 deletions startup/configure_nginx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from subprocess import call
import os
from urllib.parse import quote, unquote

NGINX_FILE = "/etc/nginx/nginx.conf"

# Replace base url placeholders with actual base url -> should
decoded_base_url = unquote("/" + os.getenv("MAIN_USER", ""))
call("sed -i 's@{WORKSPACE_BASE_URL_DECODED}@" + decoded_base_url + "@g' " + NGINX_FILE, shell=True)

# Set url escaped url
encoded_base_url = quote(decoded_base_url, safe="/%")
call("sed -i 's@{WORKSPACE_BASE_URL_ENCODED}@" + encoded_base_url + "@g' " + NGINX_FILE, shell=True)

jupyter_server_port = os.getenv("JUPYTER_SERVER_PORT")
call("sed -i 's@{JUPYTER_SERVER_PORT}@" + jupyter_server_port + "@g' " + NGINX_FILE, shell=True)

code_server_port = os.getenv("CODE_SERVER_PORT")
call("sed -i 's@{CODE_SERVER_PORT}@" + code_server_port + "@g' " + NGINX_FILE, shell=True)

# confusingly, it is the env variable "NO_VNC_PORT" that defines the port that
# KASMvnc serves its VNC through. This is set by the underlying KASM base image
vnc_port = os.getenv("NO_VNC_PORT")
call("sed -i 's@{VNC_PORT}@" + vnc_port + "@g' " + NGINX_FILE, shell=True)
Loading