Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 27 additions & 6 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,26 @@ on:
workflow_call:
outputs:
artifact:
description: The name of the uploaded image aretfact.
description: The name of the uploaded image artefact.
value: ${{ jobs.build.outputs.artifact }}
version:
description: The package's version.
value: ${{ jobs.build.outputs.version }}

jobs:
build:
name: Build snekbox-venv image
name: Build snekbox-integration image
runs-on: ubuntu-latest
services:
registry:
image: registry:2
ports:
- 5000:5000
Comment on lines +15 to +19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of hosting a registry in our job?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a registry allows us to use the newly built ghcr.io/python-discord/snekbox image for our snekbox-pydis build (you will see later that images are pushed to localhost:5000).

https://stackoverflow.com/a/63927832

There is probably some combination of cache-to/cache-from that may also work here but creating a local registry is a 3 second operation that then ensure we are using the right images and there will be no attempt made to pull images if the cache operations fail for whatever reason.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot that steps within the same job don't share the cache. But I thought that's what the existing cache-to/from already solve.

outputs:
artifact: ${{ env.artifact }}
version: ${{ steps.version.outputs.version }}
env:
artifact: image_artifact_snekbox-venv
artifact: image_artifact_snekbox-integration

steps:
- name: Checkout code
Expand All @@ -38,6 +43,8 @@ jobs:
# the builds. See https://github.com/docker/build-push-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: network=host

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
Expand Down Expand Up @@ -78,16 +85,30 @@ jobs:
with:
context: .
file: ./Dockerfile
push: false
push: true
target: venv
build-args: DEV=1
outputs: type=docker,dest=${{ env.artifact }}.tar
cache-from: |
${{ steps.cache_config.outputs.cache_from }}
ghcr.io/python-discord/snekbox-base:latest
ghcr.io/python-discord/snekbox-venv:latest
cache-to: ${{ steps.cache_config.outputs.cache_to }}
tags: ghcr.io/python-discord/snekbox-venv:${{ steps.version.outputs.version }}
tags: localhost:5000/local/snekbox-venv:${{ steps.version.outputs.version }}

- name: Build integration image for testing
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.pydis
push: false
pull: false
outputs: type=docker,dest=${{ env.artifact }}.tar
build-args: SNEKBOX_IMAGE=localhost:5000/local/snekbox-venv:${{ steps.version.outputs.version }}
cache-from: |
${{ steps.cache_config.outputs.cache_from }}
ghcr.io/python-discord/snekbox-venv:latest
cache-to: ${{ steps.cache_config.outputs.cache_to }}
tags: ghcr.io/python-discord/snekbox-integration:${{ steps.version.outputs.version }}

# Make the image available as an artifact so other jobs will be able to
# download it.
Expand Down
22 changes: 21 additions & 1 deletion .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ jobs:
deploy:
name: Build, push, & deploy
runs-on: ubuntu-latest
services:
registry:
image: registry:2
ports:
- 5000:5000

steps:
- name: Download image artifact
Expand Down Expand Up @@ -58,6 +63,21 @@ jobs:
tags: |
ghcr.io/python-discord/snekbox:latest
ghcr.io/python-discord/snekbox:${{ inputs.version }}
localhost:5000/local/snekbox:${{ inputs.version }}

- name: Build PyDis final image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.pydis
push: true
cache-from: |
ghcr.io/python-discord/snekbox:latest-pydis
build-args: SNEKBOX_IMAGE=localhost:5000/local/snekbox:${{ inputs.version }}
cache-to: type=inline
tags: |
ghcr.io/python-discord/snekbox:latest-pydis
ghcr.io/python-discord/snekbox:${{ inputs.version }}-pydis

# Deploy to Kubernetes.
- name: Install kubectl
Expand All @@ -74,7 +94,7 @@ jobs:
with:
namespace: snekbox
manifests: deployment.yaml
images: 'ghcr.io/python-discord/snekbox:${{ inputs.version }}'
images: 'ghcr.io/python-discord/snekbox:${{ inputs.version }}-pydis'

# Push the base image to GHCR, with an inline cache manifest.
- name: Push base image
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Run tests
id: run_tests
run: |
export IMAGE_SUFFIX='-venv:${{ inputs.version }}'
export IMAGE_SUFFIX='-integration:${{ inputs.version }}'
docker compose run \
--rm -T -e COVERAGE_DATAFILE=.coverage \
--entrypoint coverage \
Expand Down Expand Up @@ -110,5 +110,5 @@ jobs:
# This is to ensure that deployment won't fail at that step
- name: Install eval deps
run: |
export IMAGE_SUFFIX='-venv:${{ inputs.version }}'
export IMAGE_SUFFIX='-integration:${{ inputs.version }}'
docker compose run --rm -T --entrypoint /bin/bash snekbox scripts/install_eval_deps.sh
51 changes: 5 additions & 46 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,9 @@ RUN git clone -b master --single-branch https://github.com/google/nsjail.git . \
&& git checkout dccf911fd2659e7b08ce9507c25b2b38ec2c5800
RUN make

# ------------------------------------------------------------------------------
FROM buildpack-deps:bookworm AS builder-py-base

ENV PYENV_ROOT=/pyenv \
PYTHON_CONFIGURE_OPTS='--disable-test-modules --enable-optimizations \
--with-lto --without-ensurepip'

RUN apt-get -y update \
&& apt-get install -y --no-install-recommends \
libxmlsec1-dev \
tk-dev \
lsb-release \
software-properties-common \
gnupg \
&& rm -rf /var/lib/apt/lists/*

RUN git clone -b v2.6.9 --depth 1 https://github.com/pyenv/pyenv.git $PYENV_ROOT

COPY --link scripts/build_python.sh /
FROM python:3.14-slim-bookworm AS base

# ------------------------------------------------------------------------------
FROM builder-py-base AS builder-py-3_13
RUN /build_python.sh 3.13.8
# ------------------------------------------------------------------------------
FROM builder-py-base AS builder-py-3_14
RUN /build_python.sh 3.14.0
# ------------------------------------------------------------------------------
FROM builder-py-base AS builder-py-3_14t
RUN /build_python.sh 3.14.0t
# ------------------------------------------------------------------------------
FROM builder-py-base AS builder-py-3_14j

# Following guidance from https://github.com/python/cpython/blob/main/Tools/jit/README.md
RUN curl -o /tmp/llvm.sh https://apt.llvm.org/llvm.sh \
&& chmod +x /tmp/llvm.sh \
&& /tmp/llvm.sh 19 \
&& rm /tmp/llvm.sh

RUN /build_python.sh 3.14.0j
# ------------------------------------------------------------------------------
FROM python:3.13-slim-bookworm AS base
RUN mkdir -p /snekbin/python/

ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_CACHE_DIR=false
Expand All @@ -70,13 +32,10 @@ RUN apt-get -y update \
&& rm -rf /var/lib/apt/lists/*

COPY --link --from=builder-nsjail /nsjail/nsjail /usr/sbin/
COPY --link --from=builder-py-3_13 /snekbin/ /snekbin/
COPY --link --from=builder-py-3_14 /snekbin/ /snekbin/
COPY --link --from=builder-py-3_14t /snekbin/ /snekbin/
COPY --link --from=builder-py-3_14j /snekbin/ /snekbin/

# Snekbox defaults to using system Python unless additional versions are added.
RUN chmod +x /usr/sbin/nsjail \
&& ln -s /snekbin/python/3.14/ /snekbin/python/default
&& ln -s /usr/local /snekbin/python/default

# ------------------------------------------------------------------------------
FROM base AS venv
Expand All @@ -97,7 +56,7 @@ RUN if [ -n "${DEV}" ]; \
then \
pip install -U -r requirements/coverage.pip \
&& export PYTHONUSERBASE=/snekbox/user_base \
&& /snekbin/python/default/bin/python -m pip install --user numpy~=2.3.3; \
&& /snekbin/python/default/bin/python -m pip install --user numpy~=2.3.4; \
fi

# At the end to avoid re-installing dependencies when only a config changes.
Expand Down
14 changes: 14 additions & 0 deletions Dockerfile.pydis
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This is a custom additional build of Snekbox that includes multiple Python versions.

# The Python versions are now pulled from pre-built images to reduce build time
# and complexity. The images are built in the python-builds repository.

ARG SNEKBOX_IMAGE=ghcr.io/python-discord/snekbox:latest
FROM ${SNEKBOX_IMAGE}

COPY --link --from=ghcr.io/python-discord/python-builds:3.13 /snekbin/ /snekbin/
COPY --link --from=ghcr.io/python-discord/python-builds:3.14 /snekbin/ /snekbin/
COPY --link --from=ghcr.io/python-discord/python-builds:3.14t /snekbin/ /snekbin/
COPY --link --from=ghcr.io/python-discord/python-builds:3.14j /snekbin/ /snekbin/

RUN rm /snekbin/python/default && ln -s /snekbin/python/3.14/ /snekbin/python/default
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,24 @@ Name | Description
`SNEKBOX_DEBUG` | Enable debug logging if set to a non-empty value.
`SNEKBOX_SENTRY_DSN` | [Data Source Name] for Sentry. Sentry is disabled if left unset.

### Additional Interpreters

By default, snekbox will use and make available the system Python (the version used to run snekbox itself). Additional interpreters or binaries should be placed in `/snekbin/` which is mounted to the nsjail container.

You can use the `executable_path` parameter in `POST` requests to `/eval` to select an interpreter, e.g.

```js
{
"executable_path": "/snekbin/python/3.14/bin/python",
// Rest of /eval body
...
}
```

The default interpreter is at `/snekbin/python/default/bin/python`, you can symlink `/snekbin/python/default` to another interpreter such as `/snekbin/python/3.14` to change this default.

See [`Dockerfile.pydis`](Dockerfile.pydis) for an example using additional prebuilt interpreters and how to change the defaults.. This uses images built from [`python-discord/python-builds`](https://github.com/python-discord/python-builds).

## Third-party Packages

By default, the Python interpreter has no access to any packages besides the standard library. Even snekbox's own dependencies like Falcon and Gunicorn are not exposed.
Expand Down
4 changes: 2 additions & 2 deletions deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ spec:
spec:
initContainers:
- name: deps-install
image: ghcr.io/python-discord/snekbox:latest
image: ghcr.io/python-discord/snekbox:latest-pydis
imagePullPolicy: Always
volumeMounts:
- name: snekbox-user-base-volume
Expand All @@ -25,7 +25,7 @@ spec:
- scripts/install_eval_deps.sh
containers:
- name: snekbox
image: ghcr.io/python-discord/snekbox:latest
image: ghcr.io/python-discord/snekbox:latest-pydis
imagePullPolicy: Always
ports:
- containerPort: 8060
Expand Down
6 changes: 2 additions & 4 deletions requirements/coverage.pip
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,5 @@
#
# pip-compile --output-file=requirements/coverage.pip requirements/coverage.in
#
coverage[toml]==7.6.9
# via
# -r requirements/coverage.in
# coverage
coverage[toml]==7.11.0
# via -r requirements/coverage.in
14 changes: 7 additions & 7 deletions requirements/lint.pip
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
#
cfgv==3.4.0
# via pre-commit
distlib==0.3.9
distlib==0.4.0
# via virtualenv
filelock==3.16.1
filelock==3.20.0
# via virtualenv
identify==2.6.3
identify==2.6.15
# via pre-commit
nodeenv==1.9.1
# via pre-commit
platformdirs==4.3.6
platformdirs==4.5.0
# via virtualenv
pre-commit==4.0.1
pre-commit==4.3.0
# via -r requirements/lint.in
pyyaml==6.0.2
pyyaml==6.0.3
# via pre-commit
virtualenv==20.28.0
virtualenv==20.35.3
# via pre-commit
14 changes: 5 additions & 9 deletions requirements/pip-tools.pip
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,15 @@
#
# pip-compile --output-file=requirements/pip-tools.pip requirements/pip-tools.in
#
build==1.2.2.post1
build==1.3.0
# via pip-tools
click==8.1.7
click==8.3.0
# via pip-tools
colorama==0.4.6
packaging==25.0
# via
# -c /Users/joe/Projects/python-discord/snekbox/requirements/requirements.pip
# build
# click
packaging==24.2
# via
# -c C:\Users\chris\src\snekbox\requirements\requirements.pip
# build
pip-tools==7.4.1
pip-tools==7.5.1
# via -r requirements/pip-tools.in
pyproject-hooks==1.2.0
# via
Expand Down
26 changes: 12 additions & 14 deletions requirements/requirements.pip
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,35 @@
#
# pip-compile --extra=gunicorn --extra=sentry --output-file=requirements/requirements.pip pyproject.toml
#
attrs==24.3.0
attrs==25.4.0
# via
# jsonschema
# referencing
certifi==2024.12.14
certifi==2025.10.5
# via sentry-sdk
falcon==4.0.2
falcon==4.1.0
# via
# sentry-sdk
# snekbox (pyproject.toml)
gunicorn==23.0.0
# via snekbox (pyproject.toml)
jsonschema==4.23.0
jsonschema==4.25.1
# via snekbox (pyproject.toml)
jsonschema-specifications==2024.10.1
jsonschema-specifications==2025.9.1
# via jsonschema
packaging==24.2
packaging==25.0
# via gunicorn
protobuf==5.29.2
protobuf==6.33.0
# via snekbox (pyproject.toml)
referencing==0.35.1
referencing==0.37.0
# via
# jsonschema
# jsonschema-specifications
rpds-py==0.22.3
rpds-py==0.27.1
# via
# jsonschema
# referencing
sentry-sdk[falcon]==2.19.2
# via
# sentry-sdk
# snekbox (pyproject.toml)
urllib3==2.2.3
sentry-sdk[falcon]==2.42.0
# via snekbox (pyproject.toml)
urllib3==2.5.0
# via sentry-sdk
Loading