Skip to content
archived Visibility internal Owner erik@uvilo.com Approver _ Created 2026-05-14 Updated 2026-05-15

Container Base Image Plan 1

Scope: Prepare the dual-image migration infrastructure and build the Debian-slim Docker image locally, confirming all system tools and Python packages work on the new base before any Railway deployment. Prior plan: None


Task 1 — Duplicate the Config folder for the new Debian-slim image

The current deployment at ghcr.io/erikdakoda/librechat-git:latest (Alpine-based) must remain completely untouched and fully functional throughout the migration. To achieve this, create a parallel config folder for the Debian-slim build. Separation is enforced entirely by the folder name — individual files keep the same names.

  1. Create Forge/Configs-debian/ as a full duplicate of Forge/Configs/ (including MCP_Servers/ subfolder). All files keep their original names — no .debian suffixes.
  2. Verify that the original Forge/Configs/ folder is UNCHANGED — the current Alpine deployment must continue to work identically.
  3. The new Dockerfile will push to a SEPARATE GHCR image tag: ghcr.io/erikdakoda/librechat-git:debian-slim (NOT latest). This ensures pushing the new image does not overwrite the current Alpine image.

Task 2 — Update the Debian Dockerfile for Debian-slim

No Debian-based LibreChat image exists on GHCR — all variants are Alpine. Use a 3-stage build: copy the pre-built app from the official Alpine image, rebuild node_modules for glibc in a builder stage, then create a clean runtime image.

The final Dockerfile uses this structure:

  1. source stage — FROM ghcr.io/danny-avila/librechat:v0.8.5-rc1 — extract the pre-built LibreChat app
  2. builder stage — FROM node:20-slim — install build tools (python3, make, g++), copy /app from source, rm -rf node_modules && npm install --omit=dev for a clean glibc production install, clean npm cache
  3. runtime stage — FROM node:20-slim — install runtime system packages (git, curl, wget, nano, python3), copy uv from ghcr.io/astral-sh/uv:latest, copy /app from builder, install Python packages via uv pip install --system (no python3-pip — saves ~40MB), remove PEP 668 EXTERNALLY-MANAGED marker

Key differences from the original plan’s 2-stage approach:

  • A 3-stage build is necessary because npm rebuild on the copied musl node_modules leaves musl prebuilds alongside glibc ones (~1.4GB bloat). A fresh npm install --omit=dev in a builder stage produces clean glibc-only node_modules (~400-600MB).
  • Build tools (python3, make, g++) exist only in the builder stage, not the runtime image.
  • python3-pip is NOT installed in runtime — uv pip install --system is used instead (~40MB savings).
  • python3 IS installed in the runtime stage (needed by MCP servers and uv).

Update Forge/Configs-debian/Dockerfile.librechat with the 3-stage content. Do NOT push the image yet — that happens in Task 4.


Task 3 — Update startup.sh for Debian-slim compatibility

The new startup script at Forge/Configs-debian/startup.sh currently uses #!/bin/sh and Alpine conventions. On Debian-slim, bash is available, and the script should use it.

  1. Change the shebang from #!/bin/sh to #!/bin/bash (bash is now available)
  2. Update the config source path from Forge/Configs/LibreChat_Service/librechat.yaml to Forge/Configs-debian/librechat.yaml — the Debian startup script should reference its own config folder
  3. Review every command in the script for Alpine-specific behavior:
    • git commands: work identically on Debian
    • chmod -R 777: works identically
    • mkdir -p: works identically
    • cp: works identically
    • echo: works identically
  4. The script should not need any apk or apt-get calls — system packages are installed at build time in the Dockerfile
  5. Verify the exec npm run backend line works on the new base (it should — same LibreChat entry point)
  6. Update the comment at the top to reference the :debian-slim image tag
  7. Update Forge/Configs-debian/startup.sh with any changes

Task 4 — Build and test the Docker image locally

Build the custom image from the Debian Dockerfile and verify it starts correctly. This is the critical validation step before any Railway deployment. You need Docker installed locally and a GitHub PAT with write:packages scope.

⚠ CRITICAL: Push to ghcr.io/erikdakoda/librechat-git:debian-slim — NOT latest. The latest tag points to the current Alpine image used by the production deployment. Overwriting it would break the existing deployment (violates R6).

  1. Log in to GHCR: echo "$TOKEN" | docker login ghcr.io -u ErikDakoda --password-stdin
  2. Build: cd ~/Dev/uvilo-os/Forge/Configs-debian && docker build --pull --platform linux/amd64 -f Dockerfile.librechat -t ghcr.io/erikdakoda/librechat-git:debian-slim .
  3. Run the container locally and verify:
    • bash is available: which bash/usr/bin/bash or /bin/bash
    • curl is available: which curl
    • wget is available: which wget
    • nano is available: which nano
    • git is available: which git
    • python3 is available: which python3
    • pip packages installed: python3 -c "import mcp; import pymongo; import dns"
    • Default shell is bash: ls -la /bin/sh (on Debian this points to dash, which is fine — bash is also available)
  4. Check image size: docker images ghcr.io/erikdakoda/librechat-git:debian-slim
    • Compare with the current Alpine-based image size
    • Verify size increase is under 200MB uncompressed (Requirement R2)
    • Expected: debian-slim ~1.62GB vs alpine ~2.18GB (560MB decrease)
  5. If all checks pass, push the image: docker push ghcr.io/erikdakoda/librechat-git:debian-slim
  6. Record the image size comparison in the State document

Task 5 — Update Forge_Infrastructure.md to document the dual-image setup

The infrastructure docs must reflect that there are now two images: the current Alpine (latest tag) and the new Debian-slim (debian-slim tag). The “Alpine Container Constraints” section stays for now (it still applies to the running Alpine deployment) but a new section documents the Debian-slim image.

  1. In Forge/Forge_Infrastructure.md, add a “Dual Image Migration” section that documents:
    • Current production image: ghcr.io/erikdakoda/librechat-git:latest (Alpine-based, untouched)
    • New migration image: ghcr.io/erikdakoda/librechat-git:debian-slim (Debian-slim-based, for parallel deployment)
    • Config folders: Forge/Configs/ (Alpine) vs Forge/Configs-debian/ (Debian-slim) — same filenames, separated by folder
    • Build approach: 3-stage (source → builder → runtime) — copies app from Alpine image, rebuilds native modules for glibc
    • Once migration is complete, the Debian-slim image will be promoted to latest and Forge/Configs-debian/ will be renamed back to Forge/Configs/
  2. Add a “Debian-slim Container Notes” section that documents:
    • Available tools: bash, curl, wget, nano, git, python3 (all pre-installed)
    • Default shell: /bin/sh → dash, /bin/bash → bash
    • Package manager: apt-get (but container filesystem is read-only at runtime for node user — install packages in Dockerfile instead)
    • Python package installation: uv pip install --system (no python3-pip in runtime)
    • uv and uvx: available via COPY from ghcr.io/astral-sh/uv:latest
    • npx -y still re-downloads packages on each spawn — this hasn’t changed
    • Always include -y in npx args for LibreChat MCP servers
  3. Do NOT remove the “Alpine Container Constraints” section yet — it still applies to the running deployment