Skip to content
Merged
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
8 changes: 6 additions & 2 deletions .github/workflows/build-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ jobs:
{ fish: 3.6.1, alpine: 3.18 },
{ fish: 3.6.3, alpine: 3.19 },
{ fish: 3.7.1, alpine: 3.21 },
{ fish: 4.0.2, alpine: "edge" },
# { fish: 3.6.1, alpine: "edge" }, # priorize latest alpine version
{ fish: 4.0.2, alpine: 3.22 },
# New versions built from source
{ fish: 4.1.1, alpine: "edge", source: true },
]
fail-fast: false
steps:
Expand Down Expand Up @@ -73,6 +74,9 @@ jobs:
file: ./Dockerfile
build-contexts: |
alpine=docker-image://alpine:${{ matrix.version.alpine }}
build-args: |
FISH_VERSION=${{ matrix.version.fish }}
BUILD_FROM_SOURCE=${{ matrix.version.source == true }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
Expand Down
132 changes: 94 additions & 38 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,72 +1,128 @@
# Contributing Guide

Alpine images already [provide a `fish` package][alpine-fish] from their repository so we leverage that.
This project supports building Fish Docker images in two ways:
1. **Package installation**: Using pre-built Fish packages from Alpine repositories (faster)
2. **Source compilation**: Building Fish from GitHub source archives (supports any released version)

## Requirement
## Requirements

* [just] (a modern `make`-like system).
* [just] (a modern `make`-like system)
* Docker with buildx support

## Build supported versions
## Building Fish Versions

We provide aliases for already supported version:
The `build` recipe takes the following arguments:

```console
❯ just build-<TAB>
build-3-0-2 build-3-1-2 build-3-2-2 build-3-3-1 build-3-4-1 build-3-5-1
| Argument | Description | Default | Notes |
| ------------------ | ------------------------------ | --------- | ---------------------------- |
| `FISH_VERSION` | Fish version to build | required | e.g., "4.0.2", "4.1.1" |
| `ALPINE_VERSION` | Alpine base image version | required | e.g., "3.22", "edge" |
| `BUILD_FROM_SOURCE`| Build method | "false" | "true" for source build |
| `verbose` | Build output verbosity | "false" | "verbose" for detailed logs |

### Build from Alpine Package (Recommended)

For Fish versions available in Alpine repositories:

```fish
# Build Fish 4.0.2 from Alpine 3.22 package
just build 4.0.2 3.22

# Build from source with verbose output
just build 4.0.2 3.22 false verbose
```

## Test version
### Build from Source

Check provide version match `fish --version` output:
For newer Fish versions not yet available in Alpine packages:

just test 3.5.1
```fish
# Build Fish 4.1.1 from source
just build 4.1.1 edge true

# Build with verbose output
just build 4.1.1 edge true verbose
```

## Add a new `Alpine`/`Fish`
## Testing Versions

When a **new Fish version is available through alpine**, you can use the `build` recipe create a new image:
Verify that the built image contains the expected Fish version:

```console
❯ just build "edge" "3.5.1-r2"
```fish
# Test Fish 4.0.2 built on Alpine 3.22
just test 4.0.2 3.22
```

The `build` recipe takes 3 arguments:
The test command will:

| Argument | Description | Need |
| ---------------- | -------------------------- | ---------------------- |
| `ALPINE_VERSION` | to use as base image | **required** |
| `FISH_VERSION` | to tag the resulting image | _optional_<sup>†</sup> |
| `verbose` | debug level | _optional_ |
* Show expected vs actual Fish versions with color coding
* Display ✓ VERSION MATCH for successful matches
* Display ✗ VERSION MISMATCH for failures
* Handle missing Docker images gracefully

<sup>†</sup> Required in the CI build process.
## Adding New Fish Versions

## Build Unsupported versions
### For Versions Available in Alpine

This is **not yet supported**, but we are open for PR! :heart:
When Alpine releases a new Fish package, add it to the GitHub Actions matrix in `.github/workflows/build-images.yml`:

### Proposal
```yaml
{ fish: 4.0.7, alpine: "edge" }
```

:bulb: An idea would be to add a hook in the [Dockerfile]. e.g.:
### For Versions Not Yet in Alpine

```Dockerfile
ARG FISH_GIT_SHA=""
RUN [ -n "${FISH_GIT_SHA}" ] && make install-fish-from-source ${FISH_GIT_SHA}
For newer Fish versions, use source builds in the matrix:

```yaml
{ fish: 4.2.0, alpine: "edge", source: true }
```

And
The build system will automatically:

* Download the source archive from GitHub releases
* Compile Fish with all required dependencies (including Rust for newer versions)
* Create a Docker image with the specified Fish version

### Manual Local Builds

You can also build any Fish version locally:

```fish
# Build latest Fish from source
just build 4.1.1 edge true

```make
install-fish-from-source:
git clone --depth 1 <fish-repo-url>@${FISH_FROM_SOURCE_GIT_SHA}
<build steps>
# Build specific older version from package
just build 3.7.1 3.21 false
```

## Technical Details

### Build Methods

The Dockerfile supports two installation methods controlled by the `BUILD_FROM_SOURCE` argument:

* **Package method** (`BUILD_FROM_SOURCE=false`): Fast builds using Alpine's pre-built Fish packages
* **Source method** (`BUILD_FROM_SOURCE=true`): Compiles Fish from GitHub release archives

### Multi-stage Build

The build process uses Docker multi-stage builds:

1. **Build stage**: Downloads and compiles Fish from source (when needed)
2. **Final stage**: Creates minimal runtime image with Fish installed

### Source Build Dependencies

When building from source, the following dependencies are installed:

* Standard build tools (build-base, cmake, make)
* Fish-specific libraries (ncurses-dev, pcre2-dev, gettext-dev)
* Rust and Cargo (required for Fish 4.1+ versions)

## Release

See [RELEASE.md].

[just]: https://github.com/casey/just
[alpine-fish]: https://pkgs.alpinelinux.org/packages?name=fish&repo=&arch=
[Dockerfile]: ./Dockerfile
[RELEASE.md]: ./RELEASE.md
[RELEASE.md]: ./RELEASE.md
55 changes: 49 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,39 @@
# to specify the image version, cf. https://docs.docker.com/engine/reference/commandline/buildx_build/#build-context
# hadolint ignore=DL3007
# use 'edge' so automated build on docker hub use it instead of latest stable
FROM alpine:latest
FROM alpine:latest as base

# Build arguments
ARG FISH_VERSION=""
ARG BUILD_FROM_SOURCE="false"

# Build stage for Fish from source
FROM base as fish-builder
RUN if [ "$BUILD_FROM_SOURCE" = "true" ]; then \
apk add --no-cache --virtual .build-deps \
build-base \
cmake \
ncurses-dev \
pcre2-dev \
gettext-dev \
wget \
tar \
rust \
cargo && \
wget -O fish-${FISH_VERSION}.tar.xz "https://github.com/fish-shell/fish-shell/releases/download/${FISH_VERSION}/fish-${FISH_VERSION}.tar.xz" && \
tar -xf fish-${FISH_VERSION}.tar.xz && \
cd fish-${FISH_VERSION} && \
cmake -DCMAKE_INSTALL_PREFIX=/usr/local . && \
make && \
make install && \
apk del .build-deps; \
fi

# Final stage
FROM base as with-fish

# Copy Fish binary if built from source
COPY --from=fish-builder /usr/local /usr/local

# Common runtime dependencies of the fish shell
# We need bash for devcontainer's feature installation scripts, e.g. https://github.com/devcontainers/features/tree/main/src/git
Expand All @@ -13,14 +45,25 @@ RUN apk add --no-cache \
make \
git \
bash \
fish
libgcc \
ncurses \
pcre2 \
gettext && \
if [ "$BUILD_FROM_SOURCE" != "true" ]; then \
apk add --no-cache fish; \
fi

# Ensure fish is in PATH regardless of installation method
ENV PATH="/usr/local/bin:$PATH"

FROM with-fish AS final
WORKDIR /workspace
COPY ./makefile /workspace/
RUN make \
print-fish-version \
add-nemo-user \
move-to-container-user-home
RUN echo "$PATH"
RUN ls /usr/local/ /usr/local/bin/
RUN make print-fish-version
RUN make add-nemo-user
RUN make move-to-container-user-home

# As `nemo` user
USER nemo
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ We provide the following versions of Fish, thanks to Alpine package. Simplify sp
| `3.5.1` | `3.17` |
| `3.6.1` | `3.18` |
| `3.6.3` | `3.19` |
| `3.7.1` | `3.21` |
| `4.0.2` | `edge`, `latest` |
| `3.7.1` | `3.21` |
| `4.0.2` | `3.22` |
| `4.0.6` | `edge`, `latest` |

<!-- | `3.6.1` | `edge` | -->

Expand Down
66 changes: 23 additions & 43 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,55 +1,35 @@
set shell := ["fish", "-c"]

build ALPINE_VERSION FISH_VERSION verbose="verbose":
# Internal build recipe (prefixed with _ to indicate private)
build FISH_VERSION ALPINE_VERSION BUILD_FROM_SOURCE="false" verbose="false":
@printf "Building Fish {{ FISH_VERSION }} from {{ if BUILD_FROM_SOURCE == "true" { "source" } else { "package" } }}\n"
@printf "verbosity: %s\n\n " {{ verbose }}
docker buildx build \
{{ if verbose == "verbose" {""} else { "--quiet" } }} \
--build-context alpine=docker-image://alpine:{{ ALPINE_VERSION }} \
--build-arg FISH_VERSION={{ FISH_VERSION }} \
--build-arg BUILD_FROM_SOURCE={{ BUILD_FROM_SOURCE }} \
--file ./Dockerfile \
--tag=fish-{{ FISH_VERSION }} \
--tag=fish-{{ FISH_VERSION }}.alpine-{{ ALPINE_VERSION }} \
./

run FISH_VERSION:
run FISH_VERSION ALPINE_VERSION:
docker run \
--interactive \
--tty \
fish-{{ FISH_VERSION }}:latest

test FISH_VERSION:
docker run \
--rm \
fish-{{ FISH_VERSION }} \
'fish --version | grep {{ FISH_VERSION }}'

build-3-0-2 verbose="false": (build "3.11" "3.0.2" verbose)
run-3-0-2: (run "3.0.2")

build-3-1-2 verbose="false": (build "3.13" "3.1.2" verbose)
run-3-1-2: (run "3.1.2")

build-3-2-2 verbose="false": (build "3.14" "3.2.2" verbose)
run-3-2-2: (run "3.2.2")

build-3-3-1 verbose="false": (build "3.15" "3.3.1" verbose)
run-3-3-1: (run "3.3.1")

build-3-4-1 verbose="false": (build "3.16" "3.4.1" verbose)
run-3-4-1: (run "3.4.1")

build-3-5-1 verbose="false": (build "3.17" "3.5.1" verbose)
run-3-5-1: (run "3.5.1")

build-3-6-1 verbose="false": (build "3.18" "3.6.1" verbose)
run-3-6-1: (run "3.6.1")

build-3-6-3 verbose="false": (build "3.19" "3.6.3" verbose)
run-3-6-3: (run "3.6.3")

build-3-7-1 verbose="false": (build "3.21" "3.7.1" verbose)
run-3-7-1: (run "3.7.1")

build-4-0-2 verbose="false": (build "edge" "4.0.2" verbose)
run-4-0-2: (run "4.0.2")

# build-3-6-2 verbose="false": (build "edge" "3.6.2" verbose)
# run-3-6-2: (run "3.6.2")
fish-{{ FISH_VERSION }}.alpine-{{ ALPINE_VERSION }}

test FISH_VERSION ALPINE_VERSION:
#!/usr/bin/env fish
set expected "{{ FISH_VERSION }}"
set actual (docker run --rm fish-{{ FISH_VERSION }}.alpine-{{ ALPINE_VERSION }} 'fish --version')

if echo "$actual" | grep -q "$expected"
echo (set_color --bold --background green)"✓ fish version $expected"(set_color normal)" is correctly installed"
else
echo (set_color --bold --background red)"✗ $actual"(set_color normal)" is installed, while expecting "(set_color --background green)"$expected"(set_color normal)
exit 1
end

# Build and test in one command
build-and-test FISH_VERSION ALPINE_VERSION: (build FISH_VERSION ALPINE_VERSION) (test FISH_VERSION ALPINE_VERSION)
2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SHELL = /usr/bin/fish
SHELL = fish

CONTAINER_USER = nemo

Expand Down