Skip to content

Commit

Permalink
Make start.sh the entrypoint
Browse files Browse the repository at this point in the history
  • Loading branch information
manics committed Jan 21, 2024
1 parent a4ede5d commit f458cbe
Show file tree
Hide file tree
Showing 18 changed files with 42 additions and 43 deletions.
11 changes: 3 additions & 8 deletions docs/using/common.md
Expand Up @@ -225,21 +225,16 @@ docker run -it --rm \

### `start.sh`

The `start-notebook.py` script inherits most of its option handling capability from a more generic `start.sh` script.
The `start.sh` script supports all the features described above but allows you to specify an arbitrary command to execute.
Most of the configuration options in the `start-notebook.py` script are handled by an internal `start.sh` script tha automatically runs before the command provided to the container (it's set as the container entrypoint).
This allows you to specify an arbitrary command that takes advantage of all these features.
For example, to run the text-based `ipython` console in a container, do the following:

```bash
docker run -it --rm quay.io/jupyter/base-notebook start.sh ipython
docker run -it --rm quay.io/jupyter/base-notebook ipython
```

This script is handy when you derive a new Dockerfile from this image and install additional Jupyter applications with subcommands like `jupyter console`, `jupyter kernelgateway`, etc.

### Others

You can bypass the provided scripts and specify an arbitrary start command.
If you do, keep in mind that features, supported by the `start.sh` script and its kin, will not function (e.g., `GRANT_SUDO`).

## Conda Environments

The default Python 3.x [Conda environment](https://conda.io/projects/conda/en/latest/user-guide/concepts/environments.html) resides in `/opt/conda`.
Expand Down
3 changes: 1 addition & 2 deletions docs/using/selecting.md
Expand Up @@ -34,8 +34,7 @@ It contains:
- [mamba](https://github.com/mamba-org/mamba): "reimplementation of the conda package manager in C++". We use this package manager by default when installing packages.
- Unprivileged user `jovyan` (`uid=1000`, configurable, [see options in the common features section](./common.md) of this documentation) in group `users` (`gid=100`)
with ownership over the `/home/jovyan` and `/opt/conda` paths
- `tini` as the container entry point
- A `start.sh` script as the default command - useful for running alternative commands in the container as applications are added (e.g. `ipython`, `jupyter kernelgateway`, `jupyter lab`)
- `tini` and a `start.sh` script as the container entry point - useful for running alternative commands in the container as applications are added (e.g. `ipython`, `jupyter kernelgateway`, `jupyter lab`)
- A `run-hooks.sh` script, which can source/run files in a given directory
- Options for a passwordless sudo
- Common system libraries like `bzip2`, `ca-certificates`, `locales`
Expand Down
4 changes: 2 additions & 2 deletions images/base-notebook/start-notebook.py
Expand Up @@ -14,8 +14,8 @@
os.execvp(command[0], command)


# Wrap everything in start.sh, no matter what
command = ["/usr/local/bin/start.sh"]
# Entrypoint is start.sh
command = []

# If we want to survive restarts, tell that to start.sh
if os.environ.get("RESTARTABLE") == "yes":
Expand Down
3 changes: 2 additions & 1 deletion images/base-notebook/start-singleuser.py
Expand Up @@ -5,7 +5,8 @@
import shlex
import sys

command = ["/usr/local/bin/start.sh", "jupyterhub-singleuser"]
# Entrypoint is start.sh
command = ["jupyterhub-singleuser"]

# set default ip to 0.0.0.0
if "--ip=" not in os.environ.get("NOTEBOOK_ARGS", ""):
Expand Down
3 changes: 1 addition & 2 deletions images/docker-stacks-foundation/Dockerfile
Expand Up @@ -124,8 +124,7 @@ RUN set -x && \
fix-permissions "/home/${NB_USER}"

# Configure container startup
ENTRYPOINT ["tini", "-g", "--"]
CMD ["start.sh"]
ENTRYPOINT ["tini", "-g", "--", "start.sh"]

# Copy local files as late as possible to avoid cache busting
COPY run-hooks.sh start.sh /usr/local/bin/
Expand Down
11 changes: 11 additions & 0 deletions images/docker-stacks-foundation/start.sh
Expand Up @@ -34,6 +34,17 @@ else
cmd=( "$@" )
fi

# Backwards compatibility: start.sh is executed by default in ENTRYPOINT so
# should no longer be specified in CMD
if [ "${_START_SH_EXECUTED}" = "1" ]; then
_log "WARNING: start.sh is the default ENTRYPOINT, do not include it in CMD"
_log "Executing the command:" "${cmd[@]}"
exec "${cmd[@]}"
else
export _START_SH_EXECUTED=1
fi


# NOTE: This hook will run as the user the container was started with!
# shellcheck disable=SC1091
source /usr/local/bin/run-hooks.sh /usr/local/bin/start-notebook.d
Expand Down
2 changes: 1 addition & 1 deletion tests/all-spark-notebook/test_spark_notebooks.py
Expand Up @@ -33,7 +33,7 @@ def test_nbconvert(container: TrackedContainer, test_file: str) -> None:
timeout=60,
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True,
command=["start.sh", "bash", "-c", command],
command=["bash", "-c", command],
)

expected_file = f"{output_dir}/{test_file}.md"
Expand Down
2 changes: 1 addition & 1 deletion tests/base-notebook/test_container_options.py
Expand Up @@ -35,7 +35,7 @@ def test_nb_user_change(container: TrackedContainer) -> None:
tty=True,
user="root",
environment=[f"NB_USER={nb_user}", "CHOWN_HOME=yes"],
command=["start.sh", "bash", "-c", "sleep infinity"],
command=["bash", "-c", "sleep infinity"],
)

# Give the chown time to complete.
Expand Down
2 changes: 1 addition & 1 deletion tests/base-notebook/test_pandoc.py
Expand Up @@ -12,6 +12,6 @@ def test_pandoc(container: TrackedContainer) -> None:
logs = container.run_and_wait(
timeout=10,
tty=True,
command=["start.sh", "bash", "-c", 'echo "**BOLD**" | pandoc'],
command=["bash", "-c", 'echo "**BOLD**" | pandoc'],
)
assert "<p><strong>BOLD</strong></p>" in logs
5 changes: 1 addition & 4 deletions tests/base-notebook/test_start_container.py
Expand Up @@ -76,10 +76,7 @@ def test_tini_entrypoint(
https://superuser.com/questions/632979/if-i-know-the-pid-number-of-a-process-how-can-i-get-its-name
"""
LOGGER.info(f"Test that {command} is launched as PID {pid} ...")
running_container = container.run_detached(
tty=True,
command=["start.sh"],
)
running_container = container.run_detached(tty=True)
# Select the PID 1 and get the corresponding command
cmd = running_container.exec_run(f"ps -p {pid} -o comm=")
output = cmd.output.decode("utf-8").strip("\n")
Expand Down
2 changes: 1 addition & 1 deletion tests/docker-stacks-foundation/test_units.py
Expand Up @@ -34,5 +34,5 @@ def test_units(container: TrackedContainer) -> None:
timeout=30,
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True,
command=["start.sh", "python", f"{cont_data_dir}/{test_file_name}"],
command=["python", f"{cont_data_dir}/{test_file_name}"],
)
26 changes: 12 additions & 14 deletions tests/docker-stacks-foundation/test_user_options.py
Expand Up @@ -18,7 +18,7 @@ def test_uid_change(container: TrackedContainer) -> None:
tty=True,
user="root",
environment=["NB_UID=1010"],
command=["start.sh", "bash", "-c", "id && touch /opt/conda/test-file"],
command=["bash", "-c", "id && touch /opt/conda/test-file"],
)
assert "uid=1010(jovyan)" in logs

Expand All @@ -30,7 +30,7 @@ def test_gid_change(container: TrackedContainer) -> None:
tty=True,
user="root",
environment=["NB_GID=110"],
command=["start.sh", "id"],
command=["id"],
)
assert "gid=110(jovyan)" in logs
assert "groups=110(jovyan),100(users)" in logs
Expand All @@ -43,7 +43,7 @@ def test_nb_user_change(container: TrackedContainer) -> None:
tty=True,
user="root",
environment=[f"NB_USER={nb_user}", "CHOWN_HOME=yes"],
command=["start.sh", "bash", "-c", "sleep infinity"],
command=["bash", "-c", "sleep infinity"],
)

# Give the chown time to complete.
Expand Down Expand Up @@ -99,7 +99,6 @@ def test_chown_extra(container: TrackedContainer) -> None:
"CHOWN_EXTRA_OPTS=-R",
],
command=[
"start.sh",
"bash",
"-c",
"stat -c '%n:%u:%g' /home/jovyan/.bashrc /opt/conda/bin/jupyter",
Expand All @@ -123,7 +122,7 @@ def test_chown_home(container: TrackedContainer) -> None:
"NB_UID=1010",
"NB_GID=101",
],
command=["start.sh", "bash", "-c", "stat -c '%n:%u:%g' /home/kitten/.bashrc"],
command=["bash", "-c", "stat -c '%n:%u:%g' /home/kitten/.bashrc"],
)
assert "/home/kitten/.bashrc:1010:101" in logs

Expand All @@ -135,7 +134,7 @@ def test_sudo(container: TrackedContainer) -> None:
tty=True,
user="root",
environment=["GRANT_SUDO=yes"],
command=["start.sh", "sudo", "id"],
command=["sudo", "id"],
)
assert "uid=0(root)" in logs

Expand All @@ -147,7 +146,7 @@ def test_sudo_path(container: TrackedContainer) -> None:
tty=True,
user="root",
environment=["GRANT_SUDO=yes"],
command=["start.sh", "sudo", "which", "jupyter"],
command=["sudo", "which", "jupyter"],
)
assert logs.rstrip().endswith("/opt/conda/bin/jupyter")

Expand All @@ -158,7 +157,7 @@ def test_sudo_path_without_grant(container: TrackedContainer) -> None:
timeout=10,
tty=True,
user="root",
command=["start.sh", "which", "jupyter"],
command=["which", "jupyter"],
)
assert logs.rstrip().endswith("/opt/conda/bin/jupyter")

Expand All @@ -173,7 +172,7 @@ def test_group_add(container: TrackedContainer) -> None:
no_warnings=False,
user="1010:1010",
group_add=["users"], # Ensures write access to /home/jovyan
command=["start.sh", "id"],
command=["id"],
)
warnings = TrackedContainer.get_warnings(logs)
assert len(warnings) == 1
Expand All @@ -191,7 +190,7 @@ def test_set_uid(container: TrackedContainer) -> None:
timeout=5,
no_warnings=False,
user="1010",
command=["start.sh", "id"],
command=["id"],
)
assert "uid=1010(jovyan) gid=0(root)" in logs
warnings = TrackedContainer.get_warnings(logs)
Expand All @@ -207,7 +206,7 @@ def test_set_uid_and_nb_user(container: TrackedContainer) -> None:
user="1010",
environment=["NB_USER=kitten"],
group_add=["users"], # Ensures write access to /home/jovyan
command=["start.sh", "id"],
command=["id"],
)
assert "uid=1010(kitten) gid=0(root)" in logs
warnings = TrackedContainer.get_warnings(logs)
Expand Down Expand Up @@ -236,7 +235,7 @@ def test_container_not_delete_bind_mount(
"CHOWN_HOME=yes",
],
volumes={d: {"bind": "/home/jovyan/data", "mode": "rw"}},
command=["start.sh", "ls"],
command=["ls"],
)
assert p.read_text() == "some-content"
assert len(list(tmp_path.iterdir())) == 1
Expand All @@ -259,7 +258,6 @@ def test_jupyter_env_vars_to_unset(
"SECRET_FRUIT=mango",
],
command=[
"start.sh",
"bash",
"-c",
"echo I like $FRUIT and ${SECRET_FRUIT:-stuff}, and love ${SECRET_ANIMAL:-to keep secrets}!",
Expand All @@ -284,7 +282,7 @@ def test_secure_path(container: TrackedContainer, tmp_path: pathlib.Path) -> Non
tty=True,
user="root",
volumes={p: {"bind": "/usr/bin/python", "mode": "ro"}},
command=["start.sh", "python", "--version"],
command=["python", "--version"],
)
assert "Wrong python" not in logs
assert "Python" in logs
2 changes: 1 addition & 1 deletion tests/minimal-notebook/test_nbconvert.py
Expand Up @@ -28,7 +28,7 @@ def test_nbconvert(
timeout=30,
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True,
command=["start.sh", "bash", "-c", command],
command=["bash", "-c", command],
)
expected_file = f"{output_dir}/{test_file}.{output_format}"
assert expected_file in logs, f"Expected file {expected_file} not generated"
2 changes: 1 addition & 1 deletion tests/package_helper.py
Expand Up @@ -55,7 +55,7 @@ def start_container(container: TrackedContainer) -> Container:
LOGGER.info(f"Starting container {container.image_name} ...")
return container.run_detached(
tty=True,
command=["start.sh", "bash", "-c", "sleep infinity"],
command=["bash", "-c", "sleep infinity"],
)

@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion tests/run_command.py
Expand Up @@ -18,5 +18,5 @@ def run_command(
return container.run_and_wait(
timeout=timeout,
tty=True,
command=["start.sh", "bash", "-c", command],
command=["bash", "-c", command],
)
1 change: 0 additions & 1 deletion tests/scipy-notebook/test_cython.py
Expand Up @@ -16,7 +16,6 @@ def test_cython(container: TrackedContainer) -> None:
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True,
command=[
"start.sh",
"bash",
"-c",
# We copy our data to a temporary folder to be able to modify the directory
Expand Down
2 changes: 1 addition & 1 deletion tests/scipy-notebook/test_extensions.py
Expand Up @@ -30,5 +30,5 @@ def test_check_extension(container: TrackedContainer, extension: str) -> None:
container.run_and_wait(
timeout=10,
tty=True,
command=["start.sh", "jupyter", "labextension", "check", extension],
command=["jupyter", "labextension", "check", extension],
)
2 changes: 1 addition & 1 deletion tests/scipy-notebook/test_matplotlib.py
Expand Up @@ -42,7 +42,7 @@ def test_matplotlib(
running_container = container.run_detached(
volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro"}},
tty=True,
command=["start.sh", "bash", "-c", command],
command=["bash", "-c", command],
)
command = f"python {cont_data_dir}/{test_file}"
cmd = running_container.exec_run(command)
Expand Down

0 comments on commit f458cbe

Please sign in to comment.