Build Type Schema and UI Validation#
Dokploy's application build form uses a Zod discriminated union keyed on buildType to expose and validate only the fields relevant to the selected builder. The UI schema and the backend schema are structurally different — the UI uses strict per-variant objects while the backend uses a flat schema — and this mismatch creates gaps where backend builders respond to fields the UI never surfaces.
Entry point: ShowBuildChooseForm in apps/dokploy/components/dashboard/application/build/show.tsx.
Discriminated Union Schema#
The six-variant schema mySchema is defined locally in show.tsx and passed to react-hook-form via zodResolver. Each variant is a z.object with a z.literal discriminant:
| Build type | Extra validated fields |
|---|---|
dockerfile | dockerfile, dockerContextPath, dockerBuildStage |
heroku_buildpacks | herokuVersion |
paketo_buildpacks | (none) |
nixpacks | publishDirectory (optional string) |
railpack | railpackVersion (default "0.15.4") |
static | isStaticSpa (boolean, default false) |
The BuildType enum enumerates all six values. The default form value is nixpacks .
The backend input schema apiSaveBuildType is a flat Zod schema that picks the same set of fields but applies no per-type branching — all build-type-specific fields arrive as optional nullables regardless of which buildType was submitted.
Form State Management#
Three mechanisms keep form state consistent with the active build type:
-
resetData()— When the application data loads (or reloads after save),resetData()maps theApplicationDatarecord into the correct discriminated-union shape so the form is primed with exactly the fields the active variant expects. -
Conditional JSX — Each build-type-specific field group is wrapped in a
buildType ===guard , so only the relevant inputs render. Switching build type unmounts the previous inputs and resets their underlying form state. -
onSubmit()— Before calling the tRPC mutation,onSubmit()explicitly nulls out every field not belonging to the active build type. For example,dockerfile,dockerContextPath, anddockerBuildStageare sent asnullfor any non-Dockerfile build type.
Railpack has additional UX: the version input toggles between a predefined Select dropdown (pinned versions from RAILPACK_VERSIONS) and a free-text Input for custom versions, controlled by isManualRailpackVersion state .
The publishDirectory and isStaticSpa Gaps#
The UI schema restricts field visibility tighter than the backend actually requires, creating two notable gaps:
publishDirectory — nixpacks only in the UI, three builders at runtime#
The UI exposes publishDirectory exclusively when buildType === nixpacks . At runtime, however, three separate builders consume the field:
nixpacks.ts: WhenpublishDirectoryis set, appends--no-error-without-startto the build, then extracts artifacts from the built image viadocker cpand hands off togetStaticCommand()to generate a final nginx image.static.ts:getStaticCommand()usespublishDirectorydirectly in the generated Dockerfile'sCOPYinstruction (line 56). A blankpublishDirectorydefaults to".".docker-file.ts: WhenpublishDirectoryis present, the Dockerfile builder suppresses.envfile generation to avoid accidentally embedding secrets in a public static image.
For static and dockerfile build types, there is no UI field for publishDirectory, so the backend behavior can only be triggered by direct API calls.
isStaticSpa — static only in the UI, also invoked via nixpacks#
The UI exposes the Single Page Application checkbox only when buildType === static . However, when buildType === nixpacks with publishDirectory set, the nixpacks builder calls getStaticCommand(application) , which reads isStaticSpa from the application record to decide whether to write an nginx SPA config (try_files $uri $uri/ /index.html). Because the UI never sets isStaticSpa through the nixpacks path, nixpacks+publishDirectory builds always produce standard nginx routing even if SPA routing is desired.
Key Source Files#
| File | Purpose |
|---|---|
apps/dokploy/components/dashboard/application/build/show.tsx | UI form: discriminated union schema, field rendering, resetData, onSubmit |
packages/server/src/db/schema/application.ts | Backend apiSaveBuildType flat Zod schema |
packages/server/src/utils/builders/static.ts | getStaticCommand(): nginx Dockerfile generation, publishDirectory, isStaticSpa |
packages/server/src/utils/builders/nixpacks.ts | getNixpacksCommand(): publishDirectory artifact extraction → getStaticCommand() |
packages/server/src/utils/builders/docker-file.ts | publishDirectory security guard (suppresses .env file) |
Related PR: feat: added SPA option for static sites (#1931) — introduced isStaticSpa, the static build type's discriminated union branch, and the nginx SPA config in static.ts.