Little Sail is a drop-in replacement for the sail-* application images bundled with Laravel Sail. The images are built FROM alpine rather than ubuntu, so instead of ~1.2 GB per service you typically land around 300–500 MB. That is the entire value proposition: faster docker pull, faster cold CI, less disk churn. Nothing in your Laravel app changes — it is a Docker image substitution.
Step 1 — confirm you actually run Sail. If docker-compose.yml still has:
laravel.test:
build:
context: ./vendor/laravel/sail/runtimes/8.1
dockerfile: Dockerfile
you are on the stock image and can swap it.
Step 2 — replace the build: block with an image: reference.
services:
laravel.test:
image: neoighodaro/little-sail:8.1
ports:
- '${APP_PORT:-80}:80'
environment:
WWWUSER: '${WWWUSER}'
LARAVEL_SAIL: 1
XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
volumes:
- '.:/var/www/html'
networks:
- sail
depends_on:
- mysql
- redis
Keep the rest of the Sail skeleton (volumes, networks, depends_on, the WWWUSER/LARAVEL_SAIL env vars) intact — the image expects them.
Step 3 — rebuild and verify. Because you removed build:, you must explicitly recreate the container so Docker drops the old image layer:
sail down
sail up -d
sail php -v # confirm 8.1 and that extensions loaded
sail composer --version
sail artisan about
Where to look first. The repo's README lists the available tags (PHP 8.0 and 8.1 variants at the time of the last release). Check the tag list before assuming a version exists — there is no latest you should trust. Then compare against docker/sail/Dockerfile in your installed Sail version to see which extensions the stock image installs that Little Sail might not, and php -m inside the running container to catch gaps.
First real use case. Local dev on a small-SSD laptop where two or three Sail projects' worth of Ubuntu images (plus MySQL and Redis) is eating 8–10 GB. Swapping just the laravel.test service reclaims the bulk of it, and sail up starts noticeably faster on a cold cache.
Use it as a base layer, not just a pull. The most durable pattern is to write your own tiny Dockerfile that pins the Little Sail tag and layers on the extensions your app needs. This keeps you immune to the package being unmaintained for a specific extension:
FROM neoighodaro/little-sail:8.1
# apk, not apt — and PHP's pecl/install-php-extensions behave differently here
RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS \
&& docker-php-ext-install bcmath \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& apk del .build-deps
Then point build: at your local docker/ directory in docker-compose.yml. This is also how you upgrade past the frozen tag list.
Compose override file for team-wide opt-in. Do not edit the tracked docker-compose.yml if you have colleagues on slow networks who would rather not pull a new image. Add docker-compose.little.yml:
services:
laravel.test:
image: neoighodaro/little-sail:8.1
build: !reset
(Sail 8.x's compose supports !reset; on older versions just document that the override wins on image but you must comment out build in the base file.) Run with docker compose -f docker-compose.yml -f docker-compose.little.yml up -d.
CI jobs. For GitHub Actions, use the image directly for the PHP test job instead of shivammathur/setup-php:
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: testing
ports: ['3306:3306']
jobs:
tests:
runs-on: ubuntu-latest
container:
image: neoighodaro/little-sail:8.1
steps:
- uses: actions/checkout@v4
- run: composer install --prefer-dist --no-interaction
- run: cp .env.example .env && php artisan key:generate
- run: php artisan test
The Alpine base is the point here — a smaller image means fewer seconds spent pulling on every matrix leg.
Multi-project sharing. Because the image is a single pullable artifact rather than a per-project build, several Laravel repos on a machine share one layer set. Standardise on one tag across projects:
docker images | grep little-sail # see what you already have locally
Mixed stacks. Little Sail gives you an Alpine laravel.test, but MySQL and Redis images in Sail are still the upstream Debian ones. That asymmetry is fine — keep the database services on Sail's defaults and only shrink the app container, which is the big one anyway.
Debugging workflow. Run the same commands you would against Sail, since the entrypoint is Sail-compatible:
sail exec laravel.test sh # ash, not bash — see gotchas
sail logs -f laravel.test
docker inspect --format '{{.Config.Env}}' $(sail ps -q laravel.test 2>/dev/null)
It has not been released since February 2022. This is the single most important fact in this assessment. There are no PHP 8.2, 8.3, or 8.4 images, no arm64 variants, and no fixes for anything found since. Treat the tags as frozen artifacts: pin them explicitly, never use an unpinned tag, and plan to maintain your own FROM layer if you need to move forward. Check docker manifest inspect neoighodaro/little-sail:8.1 before committing your team to it.
Apple Silicon will hurt. If the published images are amd64-only, Docker Desktop's emulation layer makes a "smaller" image feel slower than the native Ubuntu one despite the pull advantage. Verify architecture support with docker inspect --format '{{.Architecture}}' on the pulled image before you sell this to the team on speed grounds.
musl vs glibc is the silent killer. Alpine uses musl. Things that compile fine on Sail's Ubuntu image and fail here:
apk.pecl extensions that assume glibc headers..so or binary.Symptom is usually a not found when running a binary that plainly exists on disk — that is the dynamic loader, not permissions. apk add gcompat libc6-compat sometimes papers over it; usually you rewrite the install step.
No bash. Sail muscle memory includes sail shell dropping you into bash. Alpine ships ash. Either apk add bash in your derived Dockerfile or retrain the habit to sail exec laravel.test sh.
UID/GID and file permissions. Sail's stock image creates a user matching WWWUSER/WWWGID from .env so files written in the container are owned by you on the host. Confirm Little Sail honours both variables, not just WWWUSER:
sail exec laravel.test id # should show your uid/gid, not root
touch test.txt && ls -ln test.txt
If it comes back root-owned on the host, add the usermod/chown steps yourself in a derived Dockerfile. This is the most common source of "why can't my editor save storage/logs/laravel.log" complaints.
Xdebug is off and the config path differs. XDEBUG_CONFIG and XDEBUG_MODE are passed through, but if your team relies on a mounted xdebug.ini, the stock Sail path is Ubuntu's /etc/php/8.1/...; Alpine uses /usr/local/etc/php/conf.d/. Mount accordingly or your Xdebug silently never activates. Verify with sail php -i | grep xdebug.
Composer and Node versions are frozen at the image's build date. The Composer 2.x baked in will be from early 2022. sail composer self-update works inside the container but is lost on recreate. Put RUN composer self-update in your derived Dockerfile instead, or accept the version and be aware that composer.lock content-hash mismatches on very new plugin requirements are a real possibility.
Don't fight sail:publish. Running sail artisan sail:publish regenerates docker/8.x/ and can confuse docker-compose.yml if you have half-swapped to an image. If you go the image route, remove the build: block entirely rather than leaving it dangling — Compose will otherwise rebuild the Ubuntu image on sail build --no-cache and you will wonder why your container is 1.2 GB again.
Extension points, ranked. (1) Derived Dockerfile FROM neoighodaro/little-sail:<tag> — best balance of size and control. (2) The raw apk package list from the upstream repo's Dockerfile — copy it into your own multi-stage build if you want to be fully independent. (3) Compose override file for a per-developer opt-in without touching tracked config. There is no supported plugin mechanism, no config file, and no Laravel-side integration — it is an image and nothing more, so treat every customisation as a Dockerfile concern.
Fallback if it breaks. The stock Sail runtime is one git checkout away: restore the build: context: ./vendor/laravel/sail/runtimes/<version> block and sail build --no-cache. Because Little Sail only ever touched image:/build:, the rollback is trivial — keep that diff small and isolated in a single commit so a team can revert in seconds during a demo.
How can I help you explore Laravel packages today?