2026-06-09 14:06:02 +00:00
# ppy: the single shared image every DevPlace container instance runs.
# Build once with `make ppy`. Context is devplacepy/services/containers/files.
FROM python:3.13-slim-bookworm AS base
ENV PYTHONUNBUFFERED = 1 \
PYTHONDONTWRITEBYTECODE = 1 \
PIP_NO_CACHE_DIR = 1 \
PIP_DISABLE_PIP_VERSION_CHECK = 1 \
2026-06-14 14:46:36 +00:00
PIP_DEFAULT_TIMEOUT = 120 \
PIP_RETRIES = 10 \
2026-06-09 14:06:02 +00:00
DEBIAN_FRONTEND = noninteractive \
PLAYWRIGHT_BROWSERS_PATH = /opt/playwright
RUN apt-get update && apt-get install -y --no-install-recommends \
2026-08-11 20:03:15 +02:00
git openssh-client curl wget vim ack ca-certificates build-essential libpq-dev \
2026-06-11 12:06:17 +00:00
tmux apache2-utils procps htop iftop iotop netcat-openbsd zip unzip \
2026-08-11 20:03:15 +02:00
rsync jq fakeroot xz-utils pkg-config \
2026-08-07 10:53:08 +02:00
binutils gnupg2 libc6-dev libcurl4-openssl-dev libedit2 libedit-dev \
libncurses-dev libpython3-dev libsqlite3-0 libsqlite3-dev uuid-dev \
libxml2-dev libz3-dev tzdata zlib1g-dev \
2026-06-09 14:06:02 +00:00
&& rm -rf /var/lib/apt/lists/*
2026-08-07 10:53:08 +02:00
ENV RUSTUP_HOME = /opt/rust/rustup \
CARGO_HOME = /opt/rust/cargo \
CHOOSENIM_HOME = /opt/nim/choosenim \
CHOOSENIM_DIR = /opt/nim/toolchains \
NIMBLE_DIR = /opt/nim/nimble \
SWIFTLY_HOME_DIR = /opt/swift/swiftly \
SWIFTLY_BIN_DIR = /opt/swift/bin \
SWIFT_HOME = /opt/swift/toolchain
RUN curl -fsSL https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal \
&& rm -rf " $CARGO_HOME /registry" " $CARGO_HOME /git" \
&& " $CARGO_HOME /bin/rustc" --version
RUN curl -fsSL https://nim-lang.org/choosenim/init.sh | sh -s -- -y || true; \
rm -rf " $CHOOSENIM_HOME /downloads" ; \
" $NIMBLE_DIR /bin/nim" --version | head -1
RUN set -eu; \
curl -fsSL "https://download.swift.org/swiftly/linux/swiftly- $( uname -m) .tar.gz" -o /tmp/swiftly.tar.gz; \
mkdir -p /tmp/swiftly-unpack; \
tar -xzf /tmp/swiftly.tar.gz -C /tmp/swiftly-unpack; \
/tmp/swiftly-unpack/swiftly init --assume-yes --skip-install --no-modify-profile; \
" $SWIFTLY_BIN_DIR /swiftly" install latest --assume-yes; \
toolchain = " $( find /root/.local/share/swiftly/toolchains -mindepth 1 -maxdepth 1 -type d | head -1) " ; \
[ -n " $toolchain " ] || { echo "swift toolchain not found after install" ; exit 1; } ; \
mv " $toolchain " /opt/swift/toolchain; \
rm -rf /tmp/swiftly.tar.gz /tmp/swiftly-unpack /root/.local/share/swiftly \
" $SWIFTLY_HOME_DIR " " $SWIFTLY_BIN_DIR " ; \
/opt/swift/toolchain/usr/bin/swift --version
2026-06-09 14:06:02 +00:00
FROM base AS deps
RUN pip install \
pip setuptools wheel packaging \
requests urllib3 certifi idna charset-normalizer httpx aiohttp \
pydantic typing-extensions python-dotenv aiofiles \
python-dateutil six pytz \
pyyaml toml \
loguru structlog tenacity rich tqdm click typer
RUN pip install \
numpy pandas matplotlib plotly \
sqlalchemy psycopg2-binary redis dataset
RUN pip install \
flask django fastapi starlette "uvicorn[standard]" gunicorn \
2026-06-10 07:21:05 +00:00
h2 celery apscheduler watchfiles \
2026-06-09 14:06:02 +00:00
websocket-client websockets \
beautifulsoup4 lxml scrapy
RUN pip install \
cryptography pycryptodome pillow \
pytest ruff black isort flake8 mypy \
sphinx mkdocs poetry pipenv hatch \
reflex
RUN pip install playwright && playwright install --with-deps chromium
FROM deps AS runtime
2026-08-07 13:46:40 +02:00
ARG CODE_SERVER_VERSION = 4 .131.0
RUN set -eu; \
case " $( dpkg --print-architecture) " in \
amd64) arch = amd64 ;; \
arm64) arch = arm64 ;; \
*) echo "unsupported architecture: $( dpkg --print-architecture) " ; exit 1 ;; \
esac ; \
curl -fsSL -o /tmp/code-server.tar.gz \
"https://github.com/coder/code-server/releases/download/v ${ CODE_SERVER_VERSION } /code-server- ${ CODE_SERVER_VERSION } -linux- ${ arch } .tar.gz" ; \
mkdir -p /usr/local/lib/code-server; \
tar -xzf /tmp/code-server.tar.gz -C /usr/local/lib/code-server --strip-components= 1; \
rm -f /tmp/code-server.tar.gz; \
ln -sf /usr/local/lib/code-server/bin/code-server /usr/local/bin/code-server
2026-08-10 00:23:20 +02:00
COPY vscode/devplace-workspace /usr/local/lib/code-server/lib/vscode/extensions/devplace-workspace
COPY vscode/branding/favicon.ico /usr/local/lib/code-server/src/browser/media/favicon.ico
COPY vscode/branding/favicon.svg /usr/local/lib/code-server/src/browser/media/favicon.svg
COPY vscode/branding/favicon-dark-support.svg /usr/local/lib/code-server/src/browser/media/favicon-dark-support.svg
COPY vscode/branding/pwa-icon-192.png /usr/local/lib/code-server/src/browser/media/pwa-icon-192.png
COPY vscode/branding/pwa-icon-512.png /usr/local/lib/code-server/src/browser/media/pwa-icon-512.png
COPY vscode/branding/pwa-icon-maskable-192.png /usr/local/lib/code-server/src/browser/media/pwa-icon-maskable-192.png
COPY vscode/branding/pwa-icon-maskable-512.png /usr/local/lib/code-server/src/browser/media/pwa-icon-maskable-512.png
COPY vscode/branding/devplace-login.css /tmp/devplace-login.css
COPY vscode/product.patch.json /tmp/product.patch.json
RUN set -eu; \
cat /tmp/devplace-login.css >> /usr/local/lib/code-server/src/browser/pages/login.css; \
2026-08-11 20:03:15 +02:00
python3 -c "import json,pathlib; p=pathlib.Path('/usr/local/lib/code-server/lib/vscode/product.json'); d=json.loads(p.read_text()); patch=json.loads(pathlib.Path('/tmp/product.patch.json').read_text()); d.update({k: ({**d[k], **v} if isinstance(v, dict) and isinstance(d.get(k), dict) else v) for k, v in patch.items()}); p.write_text(json.dumps(d, indent=2))" ; \
2026-08-10 00:23:20 +02:00
rm -f /tmp/devplace-login.css /tmp/product.patch.json
2026-06-09 14:06:02 +00:00
COPY sudo /usr/local/bin/sudo
2026-06-11 12:06:17 +00:00
COPY aptroot /usr/local/bin/aptroot
2026-06-09 14:06:02 +00:00
COPY pagent /usr/bin/pagent.py
2026-06-14 14:46:36 +00:00
COPY bot.py /usr/bin/botje.py
COPY d.py /usr/bin/d.py
2026-06-16 18:38:33 +00:00
COPY dpc /usr/bin/dpc
2026-06-09 14:06:02 +00:00
COPY .vimrc /home/pravda/.vimrc
RUN set -eu; \
2026-06-16 18:38:33 +00:00
chmod 0755 /usr/local/bin/sudo /usr/local/bin/aptroot /usr/bin/pagent.py /usr/bin/botje.py /usr/bin/d.py /usr/bin/dpc; \
2026-06-09 14:06:02 +00:00
ln -sf /usr/local/bin/sudo /usr/bin/sudo || true; \
2026-06-11 12:06:17 +00:00
for t in apt apt-get dpkg; do ln -sf /usr/local/bin/aptroot "/usr/local/bin/ $t " ; done ; \
2026-06-09 14:06:02 +00:00
if getent passwd 1000 >/dev/null 2>& 1; then \
existing = " $( getent passwd 1000 | cut -d: -f1) " ; \
if [ " $existing " != "pravda" ] ; then userdel " $existing " 2>/dev/null || deluser " $existing " 2>/dev/null || true; fi ; \
fi ; \
if ! getent group pravda >/dev/null 2>& 1; then groupadd --gid 1000 pravda 2>/dev/null || addgroup -g 1000 pravda 2>/dev/null || true; fi ; \
if ! id pravda >/dev/null 2>& 1; then \
useradd --uid 1000 --gid 1000 --create-home --shell /bin/bash pravda 2>/dev/null \
|| adduser -u 1000 -G pravda -D -s /bin/sh pravda 2>/dev/null || true; \
fi ; \
mkdir -p /app /home/pravda/.local/bin; \
2026-06-11 12:06:17 +00:00
for d in /app /home/pravda /usr/local/lib /usr/local/bin /usr/local/share /usr/lib/python3 /opt \
/usr/lib /usr/bin /usr/sbin /usr/share /usr/include /etc /var/lib /var/cache /var/log /srv; do \
2026-06-09 14:06:02 +00:00
[ -e " $d " ] && chown -R pravda:pravda " $d " 2>/dev/null || true; \
done
USER pravda
2026-08-07 10:53:08 +02:00
ENV PATH = /home/pravda/.local/bin:/opt/rust/cargo/bin:/opt/nim/nimble/bin:/opt/swift/toolchain/usr/bin:$PATH \
DEVPLACE_TOOLCHAINS = python,rust,nim,swift
2026-06-09 14:06:02 +00:00
WORKDIR /app
2026-08-07 10:53:08 +02:00
RUN printf '%s\n' \
'export RUSTUP_HOME=/opt/rust/rustup' \
'export CARGO_HOME=/opt/rust/cargo' \
'export NIMBLE_DIR=/opt/nim/nimble' \
'export SWIFT_HOME=/opt/swift/toolchain' \
'export DEVPLACE_TOOLCHAINS=python,rust,nim,swift' \
'export PATH=/home/pravda/.local/bin:/opt/rust/cargo/bin:/opt/nim/nimble/bin:/opt/swift/toolchain/usr/bin:$PATH' \
> /etc/profile.d/devplace-toolchains.sh \
&& chmod 0644 /etc/profile.d/devplace-toolchains.sh
RUN set -eu; \
for tool in "python --version" "rustc --version" "cargo --version" \
2026-08-07 13:46:40 +02:00
"nim --version" "nimble --version" "swift --version" \
"code-server --version" ; do \
2026-08-07 10:53:08 +02:00
$tool > /tmp/toolcheck 2>& 1 || { echo "TOOLCHAIN FAILED: $tool " ; cat /tmp/toolcheck; exit 1; } ; \
head -1 /tmp/toolcheck; \
done ; \
rm -f /tmp/toolcheck; \
for b in /usr/local/bin/sudo /usr/local/bin/aptroot /usr/bin/pagent.py \
2026-08-07 13:46:40 +02:00
/usr/bin/botje.py /usr/bin/d.py /usr/bin/dpc /usr/local/bin/code-server; do \
2026-08-07 10:53:08 +02:00
[ -x " $b " ] || { echo "missing or not executable: $b " ; exit 1; } ; \
done ; \
[ -f /home/pravda/.vimrc ] || { echo "missing /home/pravda/.vimrc" ; exit 1; }
2026-08-10 00:23:20 +02:00
RUN set -eu; \
ext = /usr/local/lib/code-server/lib/vscode/extensions/devplace-workspace; \
[ -f " $ext /package.json" ] || { echo "missing the DevPlace extension" ; exit 1; } ; \
[ -f " $ext /extension.js" ] || { echo "missing the DevPlace extension entry point" ; exit 1; } ; \
for theme in devplace-dark devplace-light; do \
python3 -c "import json,sys; json.load(open(' $ext /themes/ $theme .json'))" \
|| { echo "invalid theme: $theme " ; exit 1; } ; \
done ; \
python3 -c "import json; d=json.load(open(' $ext /package.json')); assert d['contributes']['configurationDefaults']['security.workspace.trust.enabled'] is False, 'trust default lost'" ; \
[ -f /usr/local/lib/code-server/src/browser/media/favicon.svg ] || { echo "missing the DevPlace favicon" ; exit 1; } ; \
python3 -c "import json; d=json.load(open('/usr/local/lib/code-server/lib/vscode/product.json')); assert d['nameShort']=='DevPlace', d['nameShort']; assert d['nameLong']=='DevPlace Workspace', d['nameLong']" ; \
grep -q 'devplace-login-theme' /usr/local/lib/code-server/src/browser/pages/login.css \
|| { echo "the DevPlace login stylesheet was not applied" ; exit 1; } ; \
for flag in --app-name --disable-workspace-trust --disable-getting-started-override --welcome-text; do \
code-server --help 2>& 1 | grep -q -- " $flag " \
|| { echo "code-server no longer supports $flag " ; exit 1; } ; \
done ; \
echo "DevPlace branding verified"
2026-06-09 14:06:02 +00:00
CMD [ "sleep" , "infinity" ]