This repository contains a Railway Git Releaser service that provides centralized release management for multi-service deployments. It enables you to implement Heroku-style release phases that ensure atomic deployments across multiple services.
When deploying multiple services on Railway, you often need to run shared operations (like database migrations, asset compilation, or configuration updates) before any service can be safely deployed. Railway's native preDeployCommands run independently on each service, which can lead to:
- Race conditions between services
- Partial deployments where some services deploy before migrations complete
- Inconsistent states when release steps fail for some services but not others
- No coordination between related services
The Railway Git Releaser acts as a centralized release coordinator that:
- Receives release requests from your services via API
- Queues and processes releases one at a time to prevent conflicts
- Executes your release scripts in a controlled environment
- Tracks release status and provides monitoring capabilities
- Ensures atomic deployments - all services wait for the release to complete
Imagine you have a web app with:
- Frontend service (React app)
- Backend service (API server)
- Database (PostgreSQL)
Before deploying either service, you need to:
- Run database migrations
- Build and compile frontend assets
- Update shared configuration
With this service, you can:
- Trigger a release for a specific commit
- Run all pre-deployment steps in a controlled manner
- Only deploy services after the release succeeds
- Prevent deployment if any step fails
This ensures your entire application stack deploys atomically and consistently.
This service provides a centralized release management system with the following key features:
- API-driven releases: Expose REST endpoints to trigger, queue, and monitor releases
- SQLite database: Persistent storage for release state and queue management
- Git integration: Clone repositories at specific commits and execute release commands
- Timeout protection: Configurable timeouts to prevent stuck releases
- Crash recovery: Automatic cleanup of stuck releases on service restart
- Request received: API endpoint receives release request with commit SHA
- Lock acquisition: System attempts to acquire global release lock
- Queue management: If another release is running, request is queued
- Execution: Release script is executed
- State tracking: Release status is tracked throughout the process
- Cleanup: Queue is processed and next release is triggered automatically
NODE_ENV: Defines default values for other environment variablesPORT: The port the ExpressJS server should bind toRELEASER_GIT_URL: Repository URL to cloneRELEASER_RELEASE_COMMAND: Command to execute inside the cloned repositoryRELEASE_TIMEOUT_MS: Maximum execution time (default: 30 minutes)DEFAULT_CLEANUP_DAYS: Days to keep old records (default: 30)SQLITE_DB_PATH: Database file path (default: database.sqlite)QUEUE_INTERVAL_MS: The number of milliseconds between each poll of the database to check for queued releases (default: 5000)GRACEFUL_SHUTDOWN_MS: The number of milliseconds to wait before forcefully killing a releaseAPI_KEY: The api key to use for authentication
- Install the template in your Railway project
- Configure environment variables:
RELEASER_GIT_URL=https://github.com/your-org/your-repo.git RELEASER_RELEASE_COMMAND="./scripts/release.sh" RELEASE_TIMEOUT_MS=1800000 # 30 minutes
Create a release script in your repository (e.g., scripts/release.sh):
#!/bin/bash
set -e
echo "Starting release process..."
# Run database migrations
echo "Running migrations..."
npm run migrate
# Build assets
echo "Building assets..."
npm run build
# Run tests
echo "Running tests..."
npm test
echo "Release completed successfully!"Create a pre-deploy script (in your repository) that triggers releases:
#!/bin/bash
# Trigger release for current commit
curl -X POST "https://your-releaser-service.railway.app/queue" \
-H "Content-Type: application/json" \
-d "{\"commitSha\": \"$RAILWAY_GIT_COMMIT_SHA\"}"
# Wait for release to complete
while true; do
STATUS=$(curl -s "https://your-releaser-service.railway.app/release?commit-sha=$RAILWAY_GIT_COMMIT_SHA" | jq -r '.state')
if [ "$STATUS" = "success" ]; then
echo "Release completed successfully!"
break
elif [ "$STATUS" = "failed" ]; then
echo "Release failed!"
exit 1
fi
echo "Release status: $STATUS, waiting..."
sleep 10
doneSet the preDeployCommand for all services that depend on the atomic release to run the script you created in step 3.
The service exposes the following REST API endpoints:
Health check endpoint that returns a 200 OK status if the service is running. No JSON body is returned.
Response:
Status: 200 OK
(No body)
Error Codes:
500- Internal server error
Queue a new release for a specific commit SHA.
Request Body:
{
"commitSha": "abc1234"
}Response (200 - Release queued successfully):
{
"message": "Release triggered for commit abc1234",
"release": { "id": 1, "git_commit_url": "...", "git_commit_sha": "...", "release_status": "queued", "queued_at": "2025-07-16T11:43:33.898Z", "started_at": null, "ended_at": null }
}Response (202 — Release queued/running/success/failed/timeout):
{
"message": "Release for commit abc1234 exists with status failed",
"release": { "id": 1, "git_commit_url": "...", "git_commit_sha": "...", "release_status": "failed", "queued_at": "2025-07-16T11:43:33.898Z", "started_at": "2025-07-16T11:46:48.374Z", "ended_at": "2025-07-16T11:48:05.374Z" }
}Error Codes:
400- Missing commitSha or invalid commit SHA format500- Internal server error
Cancel a queued release for a specific commit SHA.
Response (200):
{
"message": "Release for commit abc1234 removed from queue"
}Response (409 - Cannot cancel running release):
{
"error": "Cannot cancel a running release",
"state": "running"
}Error Codes:
400- Missing commit SHA409- Cannot cancel running release500- Internal server error
Get the current status of the release queue.
Response:
{
"isRunning": false,
"queueLength": 2,
"queue": [
{
"git_commit_sha": "abc1234",
"queued_at": "2024-01-01T12:00:00.000Z"
},
{
"git_commit_sha": "def5678",
"queued_at": "2024-01-01T12:01:00.000Z"
}
]
}Error Codes:
500- Internal server error
Get the release state for a specific commit SHA.
Query Parameters:
commitSha(required) - The commit SHA to check
Response:
{
"commitSha": "abc1234",
"state": "success",
"details": {
"release_status": "success",
"created_at": "2024-01-01T12:00:00.000Z",
"updated_at": "2024-01-01T12:05:00.000Z",
"started_at": "2024-01-01T12:00:00.000Z",
"ended_at": "2024-01-01T12:05:00.000Z"
}
}Error Codes:
400- Missing commit-sha parameter500- Internal server error
Get release metrics for the specified number of days.
Query Parameters:
days(optional) - Number of days to include in metrics (default: 7)
Response:
{
"period": "7 days",
"metrics": [
{
"release_status": "success",
"count": 10,
"avg_duration_minutes": 5.2
},
{
"release_status": "failed",
"count": 2,
"avg_duration_minutes": 3.1
}
]
}Error Codes:
500- Internal server error
Manually trigger cleanup of old release records.
Request Body:
{
"days": 30
}Response:
{
"message": "Cleanup completed for releases older than 30 days"
}Error Codes:
500- Internal server error
Important: The Railway Git Releaser API is publicly exposed (though not accessible) by default. This is because Railway pre-deploy steps do not have access to private networks, so the API must be reachable from the public internet for release coordination to work.
- API key protection: The service requires API key authentication for all routes except
/healthcheck. Set theAPI_KEYenvironment variable on all services that need to interact with the releaser. - Restrict access: If you are using an API gateway (such as NGINX), it is highly recommended to configure a whitelist of IPs that can access the releaser service. This typically requires enabling static outbound IPs on Railway for the services that will make API calls to the releaser.
- Minimal healthcheck exposure: The
/healthcheckroute is intentionally minimal and does not return any sensitive data—only a 200 OK status code.
Note: The API is public by design to support Railway's deployment model. Take appropriate steps to secure your deployment in production environments.
- Node.js 18+
- SQLite3
- Git
# Install dependencies
npm install
# Run tests
npm test
# Start development server
npm run devThe project includes comprehensive tests for:
- API endpoints
- Database operations
- Release lifecycle
- Queue management
- Error handling
Run tests with coverage:
npm run test:coverage- Updated README "Getting Started" instructions (env var, template link, TS scripts, check route definitions)
- Publish v1 of the Docker image to GHCR
- Build and publish template on Railway
- Allow re-running failed releases for the same commit
- Get to 90+ percent test coverage
- Add support for private repositories for release scripts
- Must make the RELEASER_GIT_URL and RELEASER_RELEASE_COMMAND be provided when queuing releases
- Support multiple service groups (groups of dependent services that need to deploy atomically)
- Adds support for multiple git repositories as sources for release scripts
- Add support for arbitrary runtimes (Deno, Bun, etc)
- Move npm cache to volume