Skip to content

Developing Locally

Once you’ve checked out the repo, you can follow this to start developing locally.

We use Mise for managing language dependencies and tasks for building and testing Railpack. You don’t have to use Mise, but it’s recommended.

Install and use all versions of tools needed for Railpack

Terminal window
# Assuming you are cd'd into the repo root
mise run setup

This command will also start a buildkit container (check out mise.toml in the root directory for more information).

Use the cli task to run the railpack CLI (this is like railpack --help)

Terminal window
mise run cli --help

If you want to compile a development build of railpack to use elsewhere on your machine:

Terminal window
mise run build
# add the railpack repo `bin/` directory to your path to use the newly-compiled railpack on your machine
export PATH="$PWD/bin:$PATH"

👋 Requirement: an instance of Buildkit must be running locally. Instructions in “Run BuildKit Locally” at the bottom of the readme.

Railpack will instantiate a BuildKit client and communicate to over GRPC in order to build the generated LLB.

Terminal window
mise run cli --verbose build examples/node-bun

Remember, mise run runs the cli in the root project directory. So, if you are in a specific project example directory, you’ll want to specify the path to the example directory as an absolute path:

Terminal window
cd examples/node-angular/
mise run cli build $(pwd)

You need to have a BuildKit instance running (see below).

You can build with a custom BuildKit frontend, but this is a bit tedious for local iteration.

The frontend needs to be built into an image and accessible to the BuildKit instance. To see how you can build and push an image, see the build-and-push-frontend mise task in mise.toml.

Once you have an image, you can do:

Generate a build plan for an app:

Terminal window
mise run cli plan examples/node-bun --out test/railpack-plan.json

Build the app with Docker:

Terminal window
docker buildx \
--build-arg BUILDKIT_SYNTAX="ghcr.io/railwayapp/railpack:railpack-frontend" \
-f test/railpack-plan.json \
examples/node-bun

or use BuildKit directly:

Terminal window
buildctl build \
--local context=examples/node-bun \
--local dockerfile=test \
--frontend=gateway.v0 \
--opt source=ghcr.io/railwayapp/railpack:railpack-frontend \
--output type=docker,name=test | docker load

Note the docker load here to load the image into Docker. However, you can change the output or push to a registry instead.

Integration tests build and run example applications in containers to verify end-to-end functionality. Each example with a test.json file gets tested automatically.

Terminal window
# Run all integration tests, this takes a long time. Let CI do this for you.
mise run test-integration
# Run specific test
mise run test-integration -- -run "TestExamplesIntegration/python-uv-tool-versions"

The test.json file contains an array of test cases. Each case builds and runs the same image but checks for different expected output strings.

In addition to a basic justBuild: true check or an output assertion, you can also run an HTTP check that starts the container and asserts that a specific route returns an expected HTTP code:

{
"httpCheck": {
"path": "/",
"expected": 200,
"internalPort": 3000
}
}

You can verify that the application outputs specific strings. expectedOutput can be a single string or an array of strings that all must be present in the output:

{
"expectedOutput": "Server running on port 3000"
}

Or with multiple strings:

{
"expectedOutput": [
"Elixir version: 1.18",
"Erlang/OTP version: 27"
]
}

Integation tests can define services (postgres, redis, anything with a docker image) that are required for the application to run. Create a docker-compose.yml in a test directory and it will automatically be picked up and run before the project container is run.

Here’s an example of how to run the container locally to manually test it:

Terminal window
docker compose up -d
docker run -it --network python-django_default --env DATABASE_URL="postgresql://django_user:django_password@postgres:5432/django_db" python-django
Terminal window
# Lint and format
mise run check
# Run tests
mise run test
# Start the docs dev server
mise run docs-dev
# Inspect what backend is being used for a given tool
mise tool poetry

Here’s some helpful debugging tricks:

  • URFAVE_CLI_TRACING=on for debugging CLI argument parsing
  • mise run cli --verbose build --show-plan --progress plain examples/node-bun
  • mise run build, add ./bin/ to your $PATH, and then run railpack in a separate local directory
  • NO_COLOR=1
  • docker exec buildkit buildctl prune to clean the builder cache