First working version
This commit is contained in:
19
.env
Normal file
19
.env
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
UID=1000 # id -u
|
||||||
|
GID=1000 # id -g
|
||||||
|
HOST_USER=spyder # Container-Benutzername
|
||||||
|
HOST_GROUP=spyder # Container-Gruppe
|
||||||
|
|
||||||
|
# Container-interne Pfade
|
||||||
|
SPYDER_HOME=/home/spyder
|
||||||
|
SPYDER_WORKSPACE=/home/spyder/workspace
|
||||||
|
|
||||||
|
# Host-Verzeichnisse für Bind-Mounts (können relativ zum Projekt sein)
|
||||||
|
SPYDER_HOME_VOLUME=./spyder-home
|
||||||
|
SPYDER_WORKSPACE_VOLUME=./spyder-workspace
|
||||||
|
|
||||||
|
# Display- und Laufzeit-Details des Host-Systems
|
||||||
|
XDG_RUNTIME_DIR=/run/user/1000
|
||||||
|
WAYLAND_DISPLAY=wayland-1
|
||||||
|
DISPLAY=:0
|
||||||
|
|
||||||
|
DEBUG=1
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
spyder-home/
|
||||||
|
spyder-workspace/
|
||||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
FROM mambaorg/micromamba:latest
|
||||||
|
ARG MAMBA_DOCKERFILE_ACTIVATE=1
|
||||||
|
|
||||||
|
ENV TZ=Europe/Berlin
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
|
||||||
|
COPY --chown=$MAMBA_USER:$MAMBA_USER environment.yml /tmp/environment.yml
|
||||||
|
RUN micromamba install -y -n base -f /tmp/environment.yml && \
|
||||||
|
micromamba clean -a -y
|
||||||
|
|
||||||
|
USER root
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends gosu && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Wrapper that starts Spyder safely for desktop sessions
|
||||||
|
COPY --chmod=0755 start-spyder.sh /usr/local/bin/start-spyder.sh
|
||||||
|
|
||||||
|
# Qt/Wayland & Matplotlib (Qt5)
|
||||||
|
ENV QT_X11_NO_MITSHM=1
|
||||||
|
ENV MPLBACKEND=Qt5Agg
|
||||||
|
|
||||||
|
# Default workspace created dynamically in entrypoint
|
||||||
|
WORKDIR /root
|
||||||
|
ENTRYPOINT ["/usr/local/bin/start-spyder.sh"]
|
||||||
|
CMD ["spyder","--new-instance"]
|
||||||
137
README.md
137
README.md
@@ -1,3 +1,136 @@
|
|||||||
# spyder-desktop-docker
|
# Spyder in Docker on Wayland
|
||||||
|
|
||||||
Dockerized Spyder IDE with native Wayland support, optional X11 fallback, GPU acceleration, and a ready-to-use Micromamba environment for reproducible Python development.
|
This stack packages the Spyder IDE inside a Docker container and supports both native Wayland and optional X11 fallback sessions on Linux desktops.
|
||||||
|
|
||||||
|
## Quick Start (local build)
|
||||||
|
|
||||||
|
1. Adjust the variables in `.env` to match your host (UID/GID, display socket paths, and bind mount locations).
|
||||||
|
2. Ensure the bind mount folders exist (the repository includes `spyder-home/` and `spyder-workspace/` by default).
|
||||||
|
3. Build and launch Spyder from source with the provided `compose.yaml`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the X11 fallback when necessary:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose --profile x11 up
|
||||||
|
```
|
||||||
|
|
||||||
|
Spyder exits when you close the IDE window. Stop the stack with `Ctrl+C`.
|
||||||
|
|
||||||
|
## Using a Prebuilt Image
|
||||||
|
|
||||||
|
Build the image locally, tag it, and push it to your registry of choice (example: `ghcr.io/your-account/spyder-wayland:latest`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose build
|
||||||
|
docker tag spyder-conda ghcr.io/your-account/spyder-wayland:latest
|
||||||
|
docker push ghcr.io/your-account/spyder-wayland:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Consumers can run the IDE without building locally using the provided `compose.runtime.yaml`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f compose.runtime.yaml up
|
||||||
|
```
|
||||||
|
|
||||||
|
Override the default image by exporting `SPYDER_IMAGE` in `.env`.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Key settings live in `.env`:
|
||||||
|
|
||||||
|
- `UID`, `GID`, `HOST_USER`, `HOST_GROUP` – mirror the host user to preserve file ownership.
|
||||||
|
- `SPYDER_HOME`, `SPYDER_WORKSPACE` – container paths for the Spyder home and project workspace.
|
||||||
|
- `SPYDER_HOME_VOLUME`, `SPYDER_WORKSPACE_VOLUME` – host-side bind mounts for persistence (can be absolute or relative paths).
|
||||||
|
- `XDG_RUNTIME_DIR`, `WAYLAND_DISPLAY`, `DISPLAY` – display/socket paths exported from the host session.
|
||||||
|
- `DEBUG` – placeholder for future debug toggles.
|
||||||
|
|
||||||
|
Update these values to relocate persisted data or point to non-standard display sockets.
|
||||||
|
|
||||||
|
### Example `.env`
|
||||||
|
|
||||||
|
```
|
||||||
|
UID=1000
|
||||||
|
GID=1000
|
||||||
|
HOST_USER=spyder
|
||||||
|
HOST_GROUP=spyder
|
||||||
|
SPYDER_HOME=/home/spyder
|
||||||
|
SPYDER_WORKSPACE=/home/spyder/workspace
|
||||||
|
SPYDER_HOME_VOLUME=./spyder-home
|
||||||
|
SPYDER_WORKSPACE_VOLUME=./spyder-workspace
|
||||||
|
XDG_RUNTIME_DIR=/run/user/1000
|
||||||
|
WAYLAND_DISPLAY=wayland-1
|
||||||
|
DISPLAY=:0
|
||||||
|
SPYDER_IMAGE=ghcr.io/your-account/spyder-wayland:latest
|
||||||
|
DEBUG=0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example `docker-compose.yml`
|
||||||
|
|
||||||
|
```
|
||||||
|
services:
|
||||||
|
spyder-wayland:
|
||||||
|
image: ${SPYDER_IMAGE}
|
||||||
|
environment:
|
||||||
|
UID: "${UID}"
|
||||||
|
GID: "${GID}"
|
||||||
|
HOST_USER: "${HOST_USER}"
|
||||||
|
HOST_GROUP: "${HOST_GROUP}"
|
||||||
|
SPYDER_HOME: "${SPYDER_HOME}"
|
||||||
|
SPYDER_WORKSPACE: "${SPYDER_WORKSPACE}"
|
||||||
|
HOME: "${SPYDER_HOME}"
|
||||||
|
WAYLAND_DISPLAY: "${WAYLAND_DISPLAY}"
|
||||||
|
XDG_RUNTIME_DIR: "${XDG_RUNTIME_DIR}"
|
||||||
|
QT_QPA_PLATFORM: "wayland"
|
||||||
|
QTWEBENGINE_DISABLE_SANDBOX: "1"
|
||||||
|
QTWEBENGINE_CHROMIUM_FLAGS: "--no-sandbox"
|
||||||
|
TZ: "Europe/Berlin"
|
||||||
|
volumes:
|
||||||
|
- ${SPYDER_HOME_VOLUME}:${SPYDER_HOME}
|
||||||
|
- ${SPYDER_WORKSPACE_VOLUME}:${SPYDER_WORKSPACE}
|
||||||
|
- ${XDG_RUNTIME_DIR}:${XDG_RUNTIME_DIR}
|
||||||
|
working_dir: ${SPYDER_WORKSPACE}
|
||||||
|
devices:
|
||||||
|
- "/dev/dri:/dev/dri"
|
||||||
|
shm_size: "1gb"
|
||||||
|
|
||||||
|
spyder-x11:
|
||||||
|
profiles:
|
||||||
|
- "x11"
|
||||||
|
image: ${SPYDER_IMAGE}
|
||||||
|
environment:
|
||||||
|
UID: "${UID}"
|
||||||
|
GID: "${GID}"
|
||||||
|
HOST_USER: "${HOST_USER}"
|
||||||
|
HOST_GROUP: "${HOST_GROUP}"
|
||||||
|
SPYDER_HOME: "${SPYDER_HOME}"
|
||||||
|
SPYDER_WORKSPACE: "${SPYDER_WORKSPACE}"
|
||||||
|
HOME: "${SPYDER_HOME}"
|
||||||
|
DISPLAY: "${DISPLAY}"
|
||||||
|
QT_QPA_PLATFORM: "xcb"
|
||||||
|
QT_X11_NO_MITSHM: "1"
|
||||||
|
QTWEBENGINE_DISABLE_SANDBOX: "1"
|
||||||
|
QTWEBENGINE_CHROMIUM_FLAGS: "--no-sandbox"
|
||||||
|
TZ: "Europe/Berlin"
|
||||||
|
volumes:
|
||||||
|
- ${SPYDER_HOME_VOLUME}:${SPYDER_HOME}
|
||||||
|
- ${SPYDER_WORKSPACE_VOLUME}:${SPYDER_WORKSPACE}
|
||||||
|
- /tmp/.X11-unix:/tmp/.X11-unix:ro
|
||||||
|
working_dir: ${SPYDER_WORKSPACE}
|
||||||
|
devices:
|
||||||
|
- "/dev/dri:/dev/dri"
|
||||||
|
shm_size: "1gb"
|
||||||
|
```
|
||||||
|
|
||||||
|
## GPU Acceleration
|
||||||
|
|
||||||
|
The configuration keeps GPU acceleration enabled. The container shares `/dev/dri` with the host; if you run into driver issues, temporarily comment out the device mapping and set `QTWEBENGINE_CHROMIUM_FLAGS` or `QT_OPENGL` in `compose.yaml` to fall back to software rendering.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The entrypoint (`start-spyder.sh`) creates a user matching the host UID/GID at runtime and cleans up stale Spyder lock files before launching the IDE.
|
||||||
|
- Python dependencies are managed via `micromamba` using `environment.yml`.
|
||||||
|
- Shared memory is increased to 1 GB to satisfy QtWebEngine requirements.
|
||||||
|
|||||||
56
compose.runtime.yaml
Normal file
56
compose.runtime.yaml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Runtime compose file that pulls a prebuilt Spyder image.
|
||||||
|
services:
|
||||||
|
spyder-wayland:
|
||||||
|
# Default Wayland session using the published image.
|
||||||
|
image: ${SPYDER_IMAGE:-ghcr.io/your-account/spyder-wayland:latest}
|
||||||
|
environment:
|
||||||
|
UID: "${UID}"
|
||||||
|
GID: "${GID}"
|
||||||
|
HOST_USER: "${HOST_USER}"
|
||||||
|
HOST_GROUP: "${HOST_GROUP}"
|
||||||
|
SPYDER_HOME: "${SPYDER_HOME}"
|
||||||
|
SPYDER_WORKSPACE: "${SPYDER_WORKSPACE}"
|
||||||
|
HOME: "${SPYDER_HOME}"
|
||||||
|
WAYLAND_DISPLAY: "${WAYLAND_DISPLAY}"
|
||||||
|
XDG_RUNTIME_DIR: "${XDG_RUNTIME_DIR}"
|
||||||
|
QT_QPA_PLATFORM: "wayland"
|
||||||
|
QTWEBENGINE_DISABLE_SANDBOX: "1"
|
||||||
|
QTWEBENGINE_CHROMIUM_FLAGS: "--no-sandbox"
|
||||||
|
TZ: "Europe/Berlin"
|
||||||
|
volumes:
|
||||||
|
- ${SPYDER_HOME_VOLUME}:${SPYDER_HOME}
|
||||||
|
- ${SPYDER_WORKSPACE_VOLUME}:${SPYDER_WORKSPACE}
|
||||||
|
- ${XDG_RUNTIME_DIR}:${XDG_RUNTIME_DIR}
|
||||||
|
working_dir: ${SPYDER_WORKSPACE}
|
||||||
|
devices:
|
||||||
|
- "/dev/dri:/dev/dri"
|
||||||
|
shm_size: "1gb"
|
||||||
|
restart: "unless-stopped"
|
||||||
|
|
||||||
|
spyder-x11:
|
||||||
|
profiles:
|
||||||
|
- "x11"
|
||||||
|
image: ${SPYDER_IMAGE:-ghcr.io/your-account/spyder-wayland:latest}
|
||||||
|
environment:
|
||||||
|
UID: "${UID}"
|
||||||
|
GID: "${GID}"
|
||||||
|
HOST_USER: "${HOST_USER}"
|
||||||
|
HOST_GROUP: "${HOST_GROUP}"
|
||||||
|
SPYDER_HOME: "${SPYDER_HOME}"
|
||||||
|
SPYDER_WORKSPACE: "${SPYDER_WORKSPACE}"
|
||||||
|
HOME: "${SPYDER_HOME}"
|
||||||
|
DISPLAY: "${DISPLAY}"
|
||||||
|
QT_QPA_PLATFORM: "xcb"
|
||||||
|
QT_X11_NO_MITSHM: "1"
|
||||||
|
QTWEBENGINE_DISABLE_SANDBOX: "1"
|
||||||
|
QTWEBENGINE_CHROMIUM_FLAGS: "--no-sandbox"
|
||||||
|
TZ: "Europe/Berlin"
|
||||||
|
volumes:
|
||||||
|
- ${SPYDER_HOME_VOLUME}:${SPYDER_HOME}
|
||||||
|
- ${SPYDER_WORKSPACE_VOLUME}:${SPYDER_WORKSPACE}
|
||||||
|
- /tmp/.X11-unix:/tmp/.X11-unix:ro
|
||||||
|
working_dir: ${SPYDER_WORKSPACE}
|
||||||
|
devices:
|
||||||
|
- "/dev/dri:/dev/dri"
|
||||||
|
shm_size: "1gb"
|
||||||
|
restart: "unless-stopped"
|
||||||
63
compose.yaml
Normal file
63
compose.yaml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Build-time compose file for the Spyder IDE stack with Wayland-native and X11 fallback frontends.
|
||||||
|
services:
|
||||||
|
spyder-wayland:
|
||||||
|
# Preferred Wayland session; shares GPU via /dev/dri.
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: spyder-conda
|
||||||
|
environment:
|
||||||
|
UID: "${UID}"
|
||||||
|
GID: "${GID}"
|
||||||
|
HOST_USER: "${HOST_USER}"
|
||||||
|
HOST_GROUP: "${HOST_GROUP}"
|
||||||
|
SPYDER_HOME: "${SPYDER_HOME}"
|
||||||
|
SPYDER_WORKSPACE: "${SPYDER_WORKSPACE}"
|
||||||
|
HOME: "${SPYDER_HOME}"
|
||||||
|
WAYLAND_DISPLAY: "${WAYLAND_DISPLAY}"
|
||||||
|
XDG_RUNTIME_DIR: "${XDG_RUNTIME_DIR}"
|
||||||
|
QT_QPA_PLATFORM: "wayland"
|
||||||
|
QTWEBENGINE_DISABLE_SANDBOX: "1"
|
||||||
|
QTWEBENGINE_CHROMIUM_FLAGS: "--no-sandbox"
|
||||||
|
TZ: "Europe/Berlin"
|
||||||
|
volumes:
|
||||||
|
- ${SPYDER_HOME_VOLUME}:${SPYDER_HOME}
|
||||||
|
- ${SPYDER_WORKSPACE_VOLUME}:${SPYDER_WORKSPACE}
|
||||||
|
- ${XDG_RUNTIME_DIR}:${XDG_RUNTIME_DIR}
|
||||||
|
working_dir: ${SPYDER_WORKSPACE}
|
||||||
|
devices:
|
||||||
|
- "/dev/dri:/dev/dri"
|
||||||
|
shm_size: "1gb"
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
spyder-x11:
|
||||||
|
# Optional X11 fallback; enable via `--profile x11`.
|
||||||
|
profiles:
|
||||||
|
- "x11"
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: spyder-conda
|
||||||
|
environment:
|
||||||
|
UID: "${UID}"
|
||||||
|
GID: "${GID}"
|
||||||
|
HOST_USER: "${HOST_USER}"
|
||||||
|
HOST_GROUP: "${HOST_GROUP}"
|
||||||
|
SPYDER_HOME: "${SPYDER_HOME}"
|
||||||
|
SPYDER_WORKSPACE: "${SPYDER_WORKSPACE}"
|
||||||
|
HOME: "${SPYDER_HOME}"
|
||||||
|
DISPLAY: "${DISPLAY}"
|
||||||
|
QT_QPA_PLATFORM: "xcb"
|
||||||
|
QT_X11_NO_MITSHM: "1"
|
||||||
|
QTWEBENGINE_DISABLE_SANDBOX: "1"
|
||||||
|
QTWEBENGINE_CHROMIUM_FLAGS: "--no-sandbox"
|
||||||
|
TZ: "Europe/Berlin"
|
||||||
|
volumes:
|
||||||
|
- ${SPYDER_HOME_VOLUME}:${SPYDER_HOME}
|
||||||
|
- ${SPYDER_WORKSPACE_VOLUME}:${SPYDER_WORKSPACE}
|
||||||
|
- /tmp/.X11-unix:/tmp/.X11-unix:ro
|
||||||
|
working_dir: ${SPYDER_WORKSPACE}
|
||||||
|
devices:
|
||||||
|
- "/dev/dri:/dev/dri"
|
||||||
|
shm_size: "1gb"
|
||||||
|
restart: "no"
|
||||||
15
environment.yml
Normal file
15
environment.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
name: spyder-wayland
|
||||||
|
channels:
|
||||||
|
- conda-forge
|
||||||
|
dependencies:
|
||||||
|
- python=3.11
|
||||||
|
- spyder>=6,<7
|
||||||
|
- spyder-kernels>=2.5
|
||||||
|
- pyqt # = PyQt5
|
||||||
|
- qt-main # Qt 5.15.x
|
||||||
|
- qt-wayland # Wayland-Plugin für Qt5
|
||||||
|
- ipykernel
|
||||||
|
- numpy
|
||||||
|
- pandas
|
||||||
|
- matplotlib
|
||||||
|
- pip
|
||||||
61
start-spyder.sh
Normal file
61
start-spyder.sh
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DEFAULT_ARGS=("spyder" "--new-instance")
|
||||||
|
|
||||||
|
TARGET_UID=${UID:-1000}
|
||||||
|
TARGET_GID=${GID:-1000}
|
||||||
|
TARGET_USER=${HOST_USER:-spyder}
|
||||||
|
TARGET_GROUP=${HOST_GROUP:-$TARGET_USER}
|
||||||
|
TARGET_HOME=${SPYDER_HOME:-/home/${TARGET_USER}}
|
||||||
|
TARGET_WORKSPACE=${SPYDER_WORKSPACE:-${TARGET_HOME}/workspace}
|
||||||
|
|
||||||
|
if [ "$#" -eq 0 ]; then
|
||||||
|
set -- "${DEFAULT_ARGS[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
existing_group=$(getent group "${TARGET_GID}" || true)
|
||||||
|
if [ -z "${existing_group}" ]; then
|
||||||
|
# Reconcile group entry so host files stay accessible.
|
||||||
|
if getent group "${TARGET_GROUP}" >/dev/null 2>&1; then
|
||||||
|
groupmod --gid "${TARGET_GID}" "${TARGET_GROUP}"
|
||||||
|
else
|
||||||
|
groupadd --gid "${TARGET_GID}" "${TARGET_GROUP}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
TARGET_GROUP=$(printf '%s' "${existing_group}" | cut -d: -f1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
existing_user=$(getent passwd "${TARGET_UID}" || true)
|
||||||
|
if [ -z "${existing_user}" ]; then
|
||||||
|
# Mirror host UID/GID inside the container.
|
||||||
|
if id "${TARGET_USER}" >/dev/null 2>&1; then
|
||||||
|
usermod --uid "${TARGET_UID}" --gid "${TARGET_GID}" --home "${TARGET_HOME}" --shell /bin/bash "${TARGET_USER}"
|
||||||
|
else
|
||||||
|
useradd --uid "${TARGET_UID}" --gid "${TARGET_GID}" --home-dir "${TARGET_HOME}" --shell /bin/bash "${TARGET_USER}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
TARGET_USER=$(printf '%s' "${existing_user}" | cut -d: -f1)
|
||||||
|
TARGET_HOME=$(printf '%s' "${existing_user}" | cut -d: -f6)
|
||||||
|
fi
|
||||||
|
|
||||||
|
install -d -m 0755 "${TARGET_HOME}" "${TARGET_WORKSPACE}"
|
||||||
|
# Ensure mounts are owned by the runtime user.
|
||||||
|
chown "${TARGET_UID}:${TARGET_GID}" "${TARGET_HOME}" "${TARGET_WORKSPACE}"
|
||||||
|
|
||||||
|
for lock in \
|
||||||
|
"${TARGET_HOME}/.config/spyder/spyder.lock" \
|
||||||
|
"${TARGET_HOME}/.local/share/spyder/spyder.lock" \
|
||||||
|
"${TARGET_HOME}/.spyder-py3/spyder.lock"
|
||||||
|
do
|
||||||
|
# Remove stale Spyder locks that can survive bind mounts.
|
||||||
|
rm -f "$lock"
|
||||||
|
done
|
||||||
|
|
||||||
|
export HOME="${TARGET_HOME}"
|
||||||
|
export USER="${TARGET_USER}"
|
||||||
|
export QTWEBENGINE_DISABLE_SANDBOX="${QTWEBENGINE_DISABLE_SANDBOX:-1}"
|
||||||
|
|
||||||
|
cd "${TARGET_WORKSPACE}"
|
||||||
|
|
||||||
|
exec gosu "${TARGET_UID}:${TARGET_GID}" micromamba run -n base "$@"
|
||||||
Reference in New Issue
Block a user