Custom Dockerfile path is ignored; Railway builds root Dockerfile instead
michf94-eng
HOBBYOP

a month ago

Hi Railway team,

I have a scheduled service sourced from GitHub where Railway is not respecting the configured custom Dockerfile.

Service: kipi-backup

Affected deployment: ff754476-e41e-4d28-afb2-a3b2482acff9

Snapshot ID: c9e284fc-c4ae-4403-bc34-6e707cc7a1f2

The Railway dashboard shows:

  • Builder: Dockerfile
  • Dockerfile Path: Dockerfile.backup

The deployment manifest also records:

  • builder: DOCKERFILE
  • dockerfilePath: Dockerfile.backup

However, the actual build uses the repository root Dockerfile.

The build stages include:

  • FROM python:3.14-slim
  • COPY pyproject.toml uv.lock README.md ./
  • COPY src ./src
  • RUN uv sync --frozen --no-dev

Dockerfile.backup instead starts from postgres:18-alpine and has a completely different build layout.

This reproduced across multiple fresh builds.

I also tested:

RAILWAY_DOCKERFILE_PATH=Dockerfile.backup

and the build still used the root Dockerfile.

Dockerfile.backup exists at the repository root and is tracked in Git. Root Directory is unset.

Could you help confirm why the build system is not honoring the custom Dockerfile path and what the supported configuration is?

Please do not make changes to the project without confirming them with me first.

I found a recent Community thread describing very similar behavior (“Dockerfile builder intermittently bypassed in favor of Railpack on the same commit”). In that case, the suggested fix was to explicitly select Dockerfile/configure it via service settings or Config as Code. In my case, those conditions are already satisfied: the Railway UI shows Builder = Dockerfile and Dockerfile Path = Dockerfile.backup, and the deployment manifest itself records builder = DOCKERFILE / dockerfilePath = Dockerfile.backup. I also tested RAILWAY_DOCKERFILE_PATH=Dockerfile.backup. Despite this, fresh builds still build the repository root Dockerfile.

Screenshot 2026-08-27 at 11.47.55 PM.png

Attachments

$10 Bounty

7 Replies

Railway
BOT

a month ago

The service has 2 staged changes that have never been applied. The last deploy attempt for those changes was rejected with the error: "Config as Code (railway.json / railway.toml) is deprecated. Use Infrastructure as Code (.railway/railway.ts) instead." The committed builder for this service is currently RAILPACK, not DOCKERFILE, which is why the root Dockerfile is auto-detected and used instead of the configured path. The dashboard is showing the staged (unapplied) values. To fix this, either migrate your config file to Infrastructure as Code, or discard the pending staged changes and set the builder to Dockerfile with the path directly in the service settings, then redeploy.


Status changed to Awaiting User Response Railway • about 1 month ago


Railway

The service has 2 staged changes that have never been applied. The last deploy attempt for those changes was rejected with the error: "Config as Code (railway.json / railway.toml) is deprecated. Use Infrastructure as Code (.railway/railway.ts) instead." The committed builder for this service is currently RAILPACK, not DOCKERFILE, which is why the root Dockerfile is auto-detected and used instead of the configured path. The dashboard is showing the staged (unapplied) values. To fix this, either migrate your config file to [Infrastructure as Code](https://docs.railway.com/infrastructure-as-code#migrating-from-config-as-code), or discard the pending staged changes and set the builder to Dockerfile with the path directly in the service settings, then redeploy.

michf94-eng
HOBBYOP

a month ago

Thanks — this is helpful, but I want to reconcile one inconsistency before changing anything.

I already migrated this service to .railway/railway.ts.

I ran a real:

railway config plan

which showed:

  • 0 to add
  • 2 to change
  • 0 to destroy

touching only kipi-backup.

I then ran:

railway config apply

and Railway reported both changes as successfully applied:

  • build.builder
  • build.dockerfilePath
  • deploy.cronSchedule
  • deploy.restartPolicyType

A subsequent railway config plan returned:

Your Railway configuration is already up to date.

Also, deployment ff754476-e41e-4d28-afb2-a3b2482acff9 has this in its own meta.serviceManifest.build snapshot:

  • builder: "DOCKERFILE"
  • dockerfilePath: "Dockerfile.backup"

However, querying the current serviceInstance.builder via GraphQL still returns RAILPACK, and the actual build still uses the repository root Dockerfile.

Could you clarify:

  1. What exactly are the 2 staged changes you see as unapplied?
  2. Why does railway config apply + an empty subsequent plan indicate the IaC was applied, while the service's committed builder still reports RAILPACK?
  3. Is it safe in this situation to discard those staged changes and configure Builder = Dockerfile / Dockerfile Path = Dockerfile.backup directly in Service Settings, while .railway/railway.ts still exists?
  4. Would the IaC file override those dashboard settings again on the next deploy/apply?

I want to avoid discarding or overwriting the wrong configuration before understanding which state Railway considers authoritative.


Status changed to Awaiting Railway Response Railway • about 1 month ago


Railway
BOT

a month ago

The 2 staged changes are a pending clear of the service's Config as Code file path (setting configFile to empty) for kipi-backup, and the last deploy attempt for that change was rejected with: "Config as Code (railway.json / railway.toml) is deprecated." The committed builder remains RAILPACK because that configFile reference was never cleared, so builds auto-detect the root Dockerfile rather than honoring the dockerfilePath. Your IaC apply likely succeeded for the fields it touched, but the underlying configFile linkage persisted, keeping the service partially in legacy mode. To resolve this, open the service's Settings in the dashboard, find the config file path field, and clear it manually, then discard the 2 staged changes from the deploy bar's overflow menu so they do not conflict. After that, set Builder to Dockerfile and Dockerfile Path to Dockerfile.backup in the same settings, and redeploy. Once the configFile reference is gone, your .railway/railway.ts becomes the sole source of truth and future railway config apply runs will commit cleanly without the dashboard overriding them.


Status changed to Awaiting User Response Railway • about 1 month ago


Railway

The 2 staged changes are a pending clear of the service's Config as Code file path (setting `configFile` to empty) for kipi-backup, and the last deploy attempt for that change was rejected with: "Config as Code (railway.json / railway.toml) is deprecated." The committed builder remains RAILPACK because that `configFile` reference was never cleared, so builds auto-detect the root Dockerfile rather than honoring the `dockerfilePath`. Your IaC apply likely succeeded for the fields it touched, but the underlying `configFile` linkage persisted, keeping the service partially in legacy mode. To resolve this, open the service's Settings in the dashboard, find the config file path field, and clear it manually, then discard the 2 staged changes from the deploy bar's overflow menu so they do not conflict. After that, set Builder to Dockerfile and Dockerfile Path to Dockerfile.backup in the same settings, and redeploy. Once the `configFile` reference is gone, your `.railway/railway.ts` becomes the sole source of truth and future `railway config apply` runs will commit cleanly without the dashboard overriding them.

michf94-eng
HOBBYOP

a month ago

I don't see any “2 staged changes”, “pending changes”, or deploy bar with pending changes anywhere in the kipi-backup service UI.

Also, I already manually cleared the Config File Path field in Settings earlier, and it currently appears empty in the dashboard.

Could you please tell me exactly where those 2 staged changes are visible in the current Railway UI, or how I can verify/clear the lingering configFile reference safely?

I do not want to discard anything blindly if the dashboard is not showing those changes.

Additional evidence: after a service settings change, kipi-backup started once and crashed. The runtime traceback confirms the built image is the main web application, not the backup image:

  • it starts /app/.venv/bin/uvicorn
  • imports /app/src/app/main.py
  • then fails during application settings initialization because ENVIRONMENT is not present

Dockerfile.backup does not contain or start Uvicorn at all.

So this confirms at runtime, not only from the build stages, that the service is still receiving the root application Dockerfile image instead of Dockerfile.backup.

The cron has now been disabled while this is being investigated.


Status changed to Awaiting Railway Response Railway • about 1 month ago


Railway
BOT

a month ago

This thread has been opened as a bounty so the community can help solve it.

Status changed to Open Railway • about 1 month ago


Railway

The 2 staged changes are a pending clear of the service's Config as Code file path (setting `configFile` to empty) for kipi-backup, and the last deploy attempt for that change was rejected with: "Config as Code (railway.json / railway.toml) is deprecated." The committed builder remains RAILPACK because that `configFile` reference was never cleared, so builds auto-detect the root Dockerfile rather than honoring the `dockerfilePath`. Your IaC apply likely succeeded for the fields it touched, but the underlying `configFile` linkage persisted, keeping the service partially in legacy mode. To resolve this, open the service's Settings in the dashboard, find the config file path field, and clear it manually, then discard the 2 staged changes from the deploy bar's overflow menu so they do not conflict. After that, set Builder to Dockerfile and Dockerfile Path to Dockerfile.backup in the same settings, and redeploy. Once the `configFile` reference is gone, your `.railway/railway.ts` becomes the sole source of truth and future `railway config apply` runs will commit cleanly without the dashboard overriding them.

michf94-eng
HOBBYOP

a month ago

I did some additional read-only investigation and now have stronger evidence that I’d like to reconcile with the staged configFile explanation.

The issue is reproducible across multiple fresh builds of the same kipi-backup service.

The service is intended to build:

Dockerfile.backup

However, the actual build continues to produce the image defined by the repository root:

Dockerfile

Evidence from the affected builds:

  • deployment ff754476-e41e-4d28-afb2-a3b2482acff9
  • deployment f4cbf72f-a1b0-4db5-a0ea-9a4c6de7ab5a

Both builds contain the root application Dockerfile stages, including:

  • Python 3.14 slim
  • COPY pyproject.toml uv.lock README.md
  • COPY src ./src
  • uv sync
  • alembic.ini
  • migrations/

They do not contain the expected Dockerfile.backup build, which starts from postgres:18-alpine.

The resulting runtime also confirmed the wrong image was built: kipi-backup attempted to start Uvicorn and import the main web application, then crashed because the web application’s ENVIRONMENT setting was absent. Dockerfile.backup does not start Uvicorn at all.

Additional checks already performed:

  • Dockerfile.backup exists at repository root and is tracked in Git.
  • Dashboard shows Builder = Dockerfile.
  • Dashboard shows Dockerfile Path = Dockerfile.backup.
  • Deployment metadata has reported builder = DOCKERFILE and dockerfilePath = Dockerfile.backup.
  • RAILWAY_DOCKERFILE_PATH=Dockerfile.backup was also tested and produced another build of the root Dockerfile.

One confusing detail remains: different Railway representations appear to disagree about builder state. A direct GraphQL inspection previously returned ServiceInstance.builder = RAILPACK, while another service-config view reports DOCKERFILE.

Given that the actual build behavior is consistently using the root Dockerfile, could you please confirm from Railway’s backend:

  1. What builder/path is actually committed for this service?
  2. Whether a lingering legacy configFile reference is still affecting build resolution.
  3. How I can safely clear that legacy state if it exists when the dashboard currently shows no pending/staged changes.
  4. Whether this could be an Infrastructure-as-Code issue where the desired DOCKERFILE state is recorded but not honored by the build pipeline.

The cron is currently disabled and the service is intentionally left stopped while this is investigated. I do not want to redeploy or modify the source configuration again until we know which state Railway considers authoritative.


turicamirabelamaria-art
PRO

a month ago

This is very likely a build-context / Dockerfile selection issue, not Docker layer caching.

Railway officially supports a custom Dockerfile through RAILWAY_DOCKERFILE_PATH (or build.dockerfilePath in railway.json). If the build log still executes instructions from the repository-root Dockerfile, first prove which file BuildKit is actually parsing.

Add a unique diagnostic line to each Dockerfile, for example:

RUN echo "ROOT_DOCKERFILE"

and in the intended Dockerfile:

RUN echo "CUSTOM_DOCKERFILE"

If the build prints ROOT_DOCKERFILE, then the custom path is genuinely being ignored.

Railway has had the same failure mode in monorepos: the service Root Directory / custom Dockerfile configuration selected the intended file for discovery, but the archive sent to BuildKit still contained the whole repository. With the root Dockerfile present in that context, BuildKit ended up parsing the wrong one.

The confirmed workaround is to make the service directory the actual archive root:

railway up ./path/to/service \

--path-as-root \

--service YOUR_SERVICE

Railway's current CLI documentation explicitly states that --path-as-root uses the supplied path as the archive root, rather than merely deploying a path inside the project context.

After doing this, verify the snapshot/build context size. It should drop from the size of the whole repository to approximately the size of that service directory, and the build log should now show the intended Dockerfile.

NO_CACHE=1 will not fix this if BuildKit is parsing the wrong Dockerfile; disabling cache only affects layers after the Dockerfile has already been selected.

I would therefore test in this order:

add unique RUN echo markers to prove which Dockerfile is parsed;

deploy with railway up ./service-dir --path-as-root --service ...;

confirm the build context/archive size is now service-scoped;

confirm the custom Dockerfile marker appears.

If that fixes it while RAILWAY_DOCKERFILE_PATH alone does not, this is reproducible evidence that Railway is selecting the root Dockerfile from the broader build context despite the configured custom Dockerfile path, and Railway should inspect the Dockerfile path → BuildKit dispatch logic.


turicamirabelamaria-art

This is very likely a build-context / Dockerfile selection issue, not Docker layer caching. Railway officially supports a custom Dockerfile through RAILWAY_DOCKERFILE_PATH (or build.dockerfilePath in railway.json). If the build log still executes instructions from the repository-root Dockerfile, first prove which file BuildKit is actually parsing. Add a unique diagnostic line to each Dockerfile, for example: RUN echo "ROOT_DOCKERFILE" and in the intended Dockerfile: RUN echo "CUSTOM_DOCKERFILE" If the build prints ROOT_DOCKERFILE, then the custom path is genuinely being ignored. Railway has had the same failure mode in monorepos: the service Root Directory / custom Dockerfile configuration selected the intended file for discovery, but the archive sent to BuildKit still contained the whole repository. With the root Dockerfile present in that context, BuildKit ended up parsing the wrong one. The confirmed workaround is to make the service directory the actual archive root: railway up ./path/to/service \ --path-as-root \ --service YOUR_SERVICE Railway's current CLI documentation explicitly states that --path-as-root uses the supplied path as the archive root, rather than merely deploying a path inside the project context. After doing this, verify the snapshot/build context size. It should drop from the size of the whole repository to approximately the size of that service directory, and the build log should now show the intended Dockerfile. NO_CACHE=1 will not fix this if BuildKit is parsing the wrong Dockerfile; disabling cache only affects layers after the Dockerfile has already been selected. I would therefore test in this order: add unique RUN echo markers to prove which Dockerfile is parsed; deploy with railway up ./service-dir --path-as-root --service ...; confirm the build context/archive size is now service-scoped; confirm the custom Dockerfile marker appears. If that fixes it while RAILWAY_DOCKERFILE_PATH alone does not, this is reproducible evidence that Railway is selecting the root Dockerfile from the broader build context despite the configured custom Dockerfile path, and Railway should inspect the Dockerfile path → BuildKit dispatch logic.

michf94-eng
HOBBYOP

a month ago

One more hypothesis to rule in/out on your end: could this be a known monorepo failure mode where the archive sent to BuildKit still contains the full repository root (including the literal Dockerfile) even when a custom dockerfilePath/Root Directory is configured? We’d rather not test railway up --path-as-root ourselves right now, since that changes the deploy source from GitHub integration to CLI upload and would disturb the reproducible state we’ve already shared with you.


Welcome!

Sign in to your Railway account to join the conversation.

Loading...