Sign inSign up

cartagodocker/zsh

By cartagodocker

β€’Updated 26 days ago

Charging zsh as shell default for root or for other existing or new users in the containers.

Image
Integration & delivery
Developer tools
Content management system
1

1.4K

cartagodocker/zsh repository overview

⁠🐳 cartagodocker/zsh

Ubuntu 24.04 LTS with zsh as the default interactive shell β€” Oh My Zsh, Powerlevel10k, eza, bat, git, passwordless sudo.

Base for others (e.g. cartagodocker/nodebun⁠). Root, uid 1000 (ubuntu), and later useradd -m users share .zshrc / .p10k.zsh / Oh My Zsh via /usr/share/globally.

The nodebun README⁠ uses the same section order. This page is the shell-base contract; NodeBun only documents runtimes it adds.

Pin a version tag. No latest in production.

FROM cartagodocker/zsh:v2.0.0

β πŸ“¦ What's in the image

PieceNotes
🐧OSUbuntu 24.04 LTS (Noble)
🐚Interactive shellzsh + Oh My Zsh + Powerlevel10k β€” CMD ["/usr/bin/zsh"]
πŸ’»Other shellsbash and sh (dash) stay installed
πŸ“‚Listing / pagereza (ls), bat, GNU find + fd, rg, less, tree, nnn, ncdu, duf β€” Utilities⁠
🧰Daily CLIEvery extra binary + our helpers: Utilities⁠
πŸ“–Cataloguedockerzsh --help β€” same inventory in-container (Catalogue CLI⁠)
🌐Networkcurl, wget, git, openssh-client (no sshd), ca-certificates, ip/ss, socat, tcpdump
πŸ”sudoNOPASSWD for every uid (ALL ALL=(ALL:ALL) NOPASSWD:ALL)
🌍LocaleLANG=C.UTF-8 · LC_ALL=C.UTF-8
🧱Build SHELL["/bin/sh", "-c"] β€” child RUN lines are POSIX, not zsh

No openssh-server, no ENTRYPOINT: docker run / Compose command: is the process.

Not in this image (shell base): gcc/g++, python3, neovim, git-lfs, rclone, locales, man-db, nmap, file, 7zip, xmllint, git-extras, gpg/gnupg, expect/Tcl, dig/nslookup (bind9-dnsutils / libicu), iperf3, rlwrap, no sshd, no Docker (dockerd / docker CLI). Those β€” or a Docker client / commit signing β€” go in a child or on the host.

Container IP: ip -4 addr. DNS: /etc/resolv.conf. Resolve: getent hosts github.com (same as curl/git). Daily CLI: Utilities⁠. Small extras (tcpdump, htpasswd) stay.


⁠🧰 Utilities

Extra CLIs we install (Ubuntu already has cp, mv, grep, awk, sed, find, top, …). Each table is name β†’ docs. Image caveats: interactive zsh/bash/sh alias lsβ†’eza and catβ†’bat (--paging=never; rcat is GNU cat); GNU ls/find stay; Debian names batcat/fdfind; jsontools uses jq (no node/python/ruby); Tab stays native (no fzf-tab / zsh-autocomplete); no vscode/fasd; no sshd; no gcc. In-container: dockerzsh --help.

⁠Shells

zsh is CMD. Interactive bash: eza/bat, zoxide, fzf keys, Tab, ble.sh, green/red ❯. Interactive sh: eza + zoxide (no fzf keys). sh -c stays POSIX. Plugins: interactive zsh only (dockerzsh --plugins).

PluginDocs
gitgit⁠
extract / xextract⁠
sudosudo⁠
jsontoolsjsontools⁠
zsh-autosuggestionszsh-autosuggestions⁠
zsh-completionszsh-completions⁠
zsh-syntax-highlightingzsh-syntax-highlighting⁠
zsh-history-substring-searchhistory-substring-search⁠
you-should-useyou-should-use⁠
safe-pastesafe-paste⁠
fancy-ctrl-zfancy-ctrl-z⁠
dirhistorydirhistory⁠
zsh-batzsh-bat⁠
⁠Listing / viewing
⁠Edit / data
⁠Archives / text
⁠Network

No dig. IP: ip -4 addr. DNS: /etc/resolv.conf. Resolve: getent hosts github.com. ping/tcpdump often need NET_RAW / NET_ADMIN.

⁠System / git / process
⁠Extras

fzf keys (Ctrl-R / Ctrl-T / Alt-C) in interactive zsh/bash only. Tab stays native. dash: no fzf keys. z is zoxide.

⁠Image helpers (ours)

Not Ubuntu packages. ssh/scp/sftp/git on PATH wrap these β€” type the normal commands.

CommandDocs
add_text_to_zshrcScripts⁠
add_text_to_p10kScripts⁠
share_config_globallyScripts⁠
sudo-passwordsudo⁠
sudo-nopasswdsudo⁠
ssh-from-hostSSH⁠
git-from-hostSSH⁠
dockerzshCatalogue⁠

β πŸ“ Image size

Uncompressed (disk / docker images)Compressed (Hub pull / layers)
ubuntu:24.04~78β€―MB~28β€―MB
Hub zsh:v1.0.5~205β€―MB~76β€―MB
zsh:v2.0.0~249β€―MB (zsh-local:dev)Hub gzip layers

v1.0.5 was zsh + eza/bat/git. v2.0.0 is a daily-driver kit without ICU/Python/gnupg/7zip. Ubuntu git still pulls Perl. Not shipped (child if needed): xmllint, git-extras, file, 7zip, gpg, expect/Tcl, dig/nslookup, iperf3, rlwrap. rsync stays (C; no Python / rrsync). docker images is uncompressed; Hub pull is gzip. Oh My Zsh + p10k are in both 1.0.5 and 2.0.0.


⁠▢️ How to run it

Two different jobs. Mix them up and it looks like β€œeza is broken”.

⁠πŸ–₯️ Interactive prompt (eza, bat, p10k)

Needs a TTY (-it or exec -it).

docker run --rm -it cartagodocker/zsh:v2.0.0
docker run --rm -it --user 1000:1000 -w /home/ubuntu cartagodocker/zsh:v2.0.0
docker exec -it <container> zsh

ls → eza. bat works. p10k is the same classic 2-line wizard as v1.0.5 (gitstatusd baked at build). Starting in / shows a lock (DIR_SHOW_WRITABLE) for uid 1000 — use -w /home/ubuntu or bind the project. Inside zsh: bash, sh, exit. Interactive bash/sh also alias ls→eza (zoxide; bash gets fzf keys) — no p10k. sh -c / Dockerfile RUN load no aliases.

⁠🧊 Keep-alive (Compose)

The container stays up without a shell. Typical:

services:
  app:
    image: cartagodocker/zsh:v2.0.0
    command: ["tail", "-f", "/dev/null"]
    user: "1000:1000"

That process is tail, not a shell, and not a TTY. No prompt, no eza aliases, no p10k. Attach:

docker compose exec app zsh     # prompt + eza + bat + p10k
docker compose exec app bash

docker run --rm image bash and docker run --rm image tail -f /dev/null work β€” no ENTRYPOINT swallows the command.


β πŸ” sudo

Default NOPASSWD (ALL ALL=(ALL:ALL) NOPASSWD:ALL). Global files (/usr/share/globally/.zshrc) are 644 root:root β€” uid 1000 cannot overwrite them. Use sudo or add_text_to_zshrc (NOPASSWD). Password is not baked; turn it on at runtime.

docker run --rm -it --user 1000:1000 cartagodocker/zsh:v2.0.0
# inside:
sudo -n id                 # default: no password

sudo-password              # TTY prompt (hidden); afterwards sudo asks
sudo-password 'secret'     # from arg (visible in `ps`)
SUDO_PASSWORD=secret sudo-password   # from env

sudo-password 'new-secret' # change it (needs the *current* sudo password
                           # once one is already required)
sudo-nopasswd              # back to NOPASSWD (needs the current password)

At start (first interactive/login shell via apply-sudo-password-on-boot.sh):

docker run --rm -it -e SUDO_PASSWORD=secret cartagodocker/zsh:v2.0.0

As uid 1000: NOPASSWD (default) escalates with sudo -n. Once a password is on, the helper uses sudo β€” type the current password, then it sets the new one (or drops it).

sudo-password writes /etc/container-sudo-password, switches sudoers NOPASSWD:ALL β†’ ALL, and chpasswds every login user. Changing it overwrites that file and re-runs chpasswd. sudo -k drops the ticket cache. Compose user: "1000:1000" drops extra groups, so the rule is ALL, not %sudo.


⁠🎨 Fonts and icons

The container only emits Unicode. The host terminal draws glyphs.

WhatNeeds on the host
πŸ”·Powerlevel10k separators, git icons, eza file iconsA Nerd Font⁠ (CaskaydiaCove / Cascadia Code NF)
🐳Prompt whale (os_icon)An emoji font β€” Segoe UI Emoji (Windows), Apple Color Emoji (macOS), Noto Color Emoji (Linux). Some Linux terminals have none β†’ tofu. Same Unicode; the font is local.

VS Code:

"terminal.integrated.fontFamily": "'Cascadia Code NF', 'CaskaydiaCove Nerd Font', Consolas, monospace"

Without a Nerd Font you get boxes on powerline / ls icons β€” not an image bug. No in-image fallback still looks like p10k.


⁠🧩 Scripts for child images

All write /usr/share/globally/... (every user). sudo if needed.

⁠add_text_to_zshrc
add_text_to_zshrc "alias hello='echo hi'"
add_text_to_zshrc "alias hello='echo hi'" --prepend
add_text_to_zshrc "$(printf '%s\n' \
    'alias hello="echo hi"' \
    'alias bye="echo bye"')"
FROM cartagodocker/zsh:v2.0.0
RUN add_text_to_zshrc "$(printf '%s\n' \
    'alias hello="echo hi"')"
⁠add_text_to_p10k

Same CLI, writes .p10k.zsh.

add_text_to_p10k "typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose"
⁠share_config_globally

Move a path from a user home (default /root) to /usr/share/<name> and symlink it for /root, /home/*, /etc/skel.

share_config_globally .local/share/fnm
share_config_globally .oh-my-zsh --to globally/.oh-my-zsh --permissions 755

--permissions default 755. Use 777 only when every uid must write the shared tree (package caches).


β πŸ”‘ SSH (client only β€” no sshd)

openssh-client is installed. Bind host ~/.ssh and type ssh / git. Root or any uid β€” one volume.

services:
  dev:
    image: cartagodocker/zsh:v2.0.0
    volumes:
      - ~/.ssh:/${USER}/.ssh:ro
      - ~/.gitconfig:/${USER}/.gitconfig:ro
docker run --rm -it \
  -v "$HOME/.ssh:/$USER/.ssh:ro" \
  -v "$HOME/.gitconfig:/$USER/.gitconfig:ro" \
  cartagodocker/zsh:v2.0.0
# inside: ssh [email protected]
#         git commit   # author = host user.name, not ubuntu@id

${USER} is the host name. Docker needs an absolute target (/${USER}/.ssh β†’ /cartago/.ssh), not ${USER}/.ssh (not $HOME in the container). The client finds any bind ending in .ssh and copies keys to a uid-owned 700 dir (OpenSSH rejects 644/777 and cannot write known_hosts on :ro). Root or any uid.

Git author is not the SSH key. git pull/push use the key; git commit needs user.name + user.email. Bind host ~/.gitconfig (or GIT_AUTHOR_NAME + GIT_AUTHOR_EMAIL). Any absolute path ending in .gitconfig works. Without it, git commit says identity is required and exits 1 β€” no invented ubuntu@<container>.

Missing keys: ssh/scp/sftp print keys are required (bind example; another directory is fine), then OpenSSH still runs.

known_hosts is optional. Binding ~/.ssh brings the host file if present.

LayerWhereWhat
Official (baked)/usr/share/ssh/known_hosts and /etc/ssh/ssh_known_hostsGitHub / GitLab keys. GlobalKnownHostsFile. Same content twice so /usr/bin/ssh still trusts them if the wrapper is bypassed.
Your host file (if present)copied / merged into /tmp/container-ssh-<uid>/known_hostsUsed as-is. New hosts (accept-new) append there, never on the :ro volume. Re-runs merge new lines and keep container-learned hosts.

Without a host known_hosts, GitHub / GitLab still work from the baked file. Other hosts are learned on first connect and stay in /tmp for this container.

Agent (key never enters the container) still works if you already use one:

docker run --rm -it \
  -v "$SSH_AUTH_SOCK":/ssh-agent \
  -e SSH_AUTH_SOCK=/ssh-agent \
  cartagodocker/zsh:v2.0.0

Not SSH-into-the-container. Attach: docker exec -it … zsh.


β πŸ“– Catalogue CLI (dockerzsh)

In-container dockerzsh catalogues every tool and helper β€” not the host docker CLI.

docker run --rm cartagodocker/zsh:v2.0.0 dockerzsh --help
docker compose exec app dockerzsh --help

The full dump is long on purpose. Filter by section:

dockerzsh --sections | -s       # list ids
dockerzsh --shells              # one section
dockerzsh --plugins             # Oh My Zsh plugins (interactive zsh)
dockerzsh shells                # same (flag or bare id)
dockerzsh --section shells      # same
dockerzsh shells network        # several sections
dockerzsh --version | -v        # image identity (ZSH_IMAGE_VERSION)
dockerzsh --list | -l           # tool names
dockerzsh --list --version | -l -v   # probe every catalogue tool now
dockerzsh shells --version | shells -v
dockerzsh eza --version | eza -v
IdSection (every tool β€” same list as Utilities⁠)
aboutWhat this image is (CMD, no ENTRYPOINT, TTY vs keep-alive)
usageHow to invoke dockerzsh
shellszsh, bash, sh
pluginsOh My Zsh plugins (interactive zsh only) β€” Utilities⁠
listingeza, bat, fd, rg, less, tree, nnn, ncdu, duf
editnano, vi, jq, jo, sqlite3, hexdump/xxd, bc, column
archivesunzip/zip, tar/gzip, xz, bzip2, zstd, lz4, pigz, cpio, cabextract, extract/x, uchardet, dos2unix
networkcurl/wget, ssh/scp/sftp, ping, tracepath, fping, whois, mtr, traceroute, rsync, nc, socat, ip/ss, openssl, tcpdump, htpasswd/ab
systemgit/tig, make, envsubst, htop, lsof, killall, pv, sponge, uuidgen, getfacl/setfacl, getcap/setcap, inotifywait, entr, strace, progress, tmux, keychain, sudo
extrasfzf, zoxide, colordiff, patch
helpersadd_text_to_zshrc, add_text_to_p10k, share_config_globally, sudo-password, sudo-nopasswd, git-from-host, dockerzsh
fontsHost fonts + what is not in this image

Unknown ids exit 2 (--sections). --shells and shells are the same id (listing also accepts --ls).


β πŸš€ Build and publish

docker build --build-arg VERSION=2.0.0 -t zsh-local:dev -f ./Dockerfile ./

GitHub Actions (secrets DOCKERHUB_USERNAME, DOCKERHUB_PASSWORD; variable DOCKERHUB_REPO):

TriggerWhat happens
Git tag v*Build + push cartagodocker/zsh:<tag> if new (skip if it exists). Create/replace a GitHub Release. No Hub :latest. β€” docker-hub-update.yml⁠
Push to main that changes README.mdHub long description (full_description ≀ ~25β€―000 chars) β€” update-dockerhub-description.yml⁠

See CHANGELOG.md⁠.

Tag summary

Content type

Image

Digest

sha256:e2219eb97…

Size

92.9 MB

Last updated

26 days ago

docker pull cartagodocker/zsh:v2.0.0