OCI Builder
Nixpacks Successor
Deploy Railpack as a Build Server
Zero-config container builds for Node, Python, Go, Ruby, PHP, Java, Rust, Elixir, Deno and more — no hand-written Dockerfiles.
At a Glance
| Project | Railpack (Railway) |
| License | MIT |
| Recommended Plan | RamNode Cloud VPS 2 vCPU / 4 GB / 50 GB+ |
| OS | Ubuntu 22.04 / 24.04 |
| Engine | BuildKit (LLB) |
1
System Prep
Update + basics
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl ca-certificates gnupg lsb-release git jq2
Install Docker
Official Docker repo
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER # log out/in for group change3
Run BuildKit
Persistent BuildKit container
docker run -d \
--name buildkit \
--restart unless-stopped \
--privileged \
-v /var/lib/buildkit:/var/lib/buildkit \
moby/buildkit:latest
echo "export BUILDKIT_HOST='docker-container://buildkit'" | sudo tee /etc/profile.d/buildkit.sh
sudo chmod +x /etc/profile.d/buildkit.sh
source /etc/profile.d/buildkit.sh
docker ps --filter name=buildkit
docker logs buildkit 2>&1 | tail -54
Install Railpack CLI
Direct binary install
RAILPACK_VERSION=$(curl -s https://api.github.com/repos/railwayapp/railpack/releases/latest | jq -r .tag_name)
ARCH=$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')
cd /tmp
curl -L "https://github.com/railwayapp/railpack/releases/download/${RAILPACK_VERSION}/railpack-${RAILPACK_VERSION}-linux-${ARCH}.tar.gz" -o railpack.tar.gz
tar -xzf railpack.tar.gz
sudo install -m 0755 railpack /usr/local/bin/railpack
railpack --version5
Build Your First Image
Build a sample Vite + React app
mkdir -p ~/builds && cd ~/builds
npm create vite@latest hello-vite -- --template react
cd hello-vite
npm install
railpack build .
docker images | grep hello-vite
docker run --rm -p 3000:3000 hello-viteOptional railpack.json overrides
{
"providers": ["node"],
"packages": { "node": "20.18.0", "pnpm": "9.x" },
"aptPackages": ["imagemagick", "ffmpeg"],
"steps": {
"build": {
"commands": ["pnpm install --frozen-lockfile", "pnpm build"]
}
},
"deploy": {
"startCommand": "pnpm start",
"variables": { "NODE_ENV": "production" }
}
}6
Push to a Container Registry
GitHub Container Registry
echo $GITHUB_TOKEN | docker login ghcr.io -u your-username --password-stdin
docker tag your-app:v1.2.3 ghcr.io/your-org/your-app:v1.2.3
docker push ghcr.io/your-org/your-app:v1.2.3Reusable build wrapper /usr/local/bin/rp-build
#!/usr/bin/env bash
set -euo pipefail
GIT_URL="$1"; IMAGE_NAME="$2"; IMAGE_TAG="$3"; REGISTRY="${4:-}"
BUILD_DIR=$(mktemp -d)
trap 'rm -rf "$BUILD_DIR"' EXIT
git clone --depth 1 --branch "${IMAGE_TAG}" "${GIT_URL}" "${BUILD_DIR}" 2>/dev/null || \
git clone --depth 1 "${GIT_URL}" "${BUILD_DIR}"
cd "${BUILD_DIR}"
railpack build . --name "${IMAGE_NAME}" --tag "${IMAGE_TAG}"
if [[ -n "${REGISTRY}" ]]; then
FULL="${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
docker tag "${IMAGE_NAME}:${IMAGE_TAG}" "${FULL}"
docker push "${FULL}"
fi7
Git Hook Auto-Build
Bare repo + post-receive hook
sudo mkdir -p /srv/git/your-app.git
sudo chown $USER:$USER /srv/git/your-app.git
cd /srv/git/your-app.git
git init --bare
cat > hooks/post-receive <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
while read oldrev newrev refname; do
branch=$(git rev-parse --symbolic --abbrev-ref "$refname")
if [[ "$branch" == "main" ]]; then
BUILD_DIR=$(mktemp -d)
git --work-tree="$BUILD_DIR" --git-dir=/srv/git/your-app.git checkout -f main
cd "$BUILD_DIR"
railpack build . --name your-app --tag "$(git rev-parse --short HEAD)"
rm -rf "$BUILD_DIR"
fi
done
EOF
chmod +x hooks/post-receive8
Cache + Resource Limits
Daily prune
sudo tee /etc/cron.daily/buildkit-prune > /dev/null <<'EOF'
#!/usr/bin/env bash
docker exec buildkit buildctl prune --keep-duration 72h >/dev/null 2>&1 || true
EOF
sudo chmod +x /etc/cron.daily/buildkit-pruneCap BuildKit resources
docker stop buildkit && docker rm buildkit
docker run -d --name buildkit --restart unless-stopped --privileged \
--memory 4g --cpus 2.0 \
-v /var/lib/buildkit:/var/lib/buildkit \
moby/buildkit:latest