HelipodDocs

Builds and Helipack

How Helipod turns a Git repository into a container image, and how to tune it with helipack.json.

When a service's source is a Git repository (or an inline Dockerfile), every deploy starts with a build. Image sources skip this step.

git clone (branch, root directory)
        │
        ├── Dockerfile in the folder? ──► docker build with your Dockerfile
        │
        └── no Dockerfile ──► Helipack: detect → generate a Dockerfile → build
                                          │
                                          ▼
                              image pushed to your private registry space, then rolled out

Your own Dockerfile

If the folder being built (the repository root, or the service's rootDirectory) contains a Dockerfile, Helipod builds it as it is. Your variables are available during the build as a BuildKit secret named build_env (a .env file), so they never end up in an image layer:

Dockerfile
FROM node:22-alpine
WORKDIR /app
COPY . .
RUN --mount=type=secret,id=build_env,target=/app/.env npm ci && npm run build
CMD ["node", "dist/server.js"]

Helipack: builds without a Dockerfile

Helipack is Helipod's zero-config builder. It reads your project, writes a multi-stage Dockerfile tuned for the framework it finds, and builds it with layer caching.

Detect

Helipack reads package.json, bun.lock / lock files, composer.json, requirements.txt / pyproject.toml, framework config files and scripts, and works out the runtime, framework, package manager, build command, start command and port.

Apply your settings

Settings from helipack.json, railway.json / railway.toml and the service settings are merged on top of what was detected (see Configuration).

Generate

It writes a multi-stage Dockerfile: dependencies are installed in a cached stage, the app is built in a second stage, and only what is needed to run goes into the final image, which runs as a non-root user.

Build and report

The image is built with cache mounts for the package manager and framework caches, so later builds reuse work. The detected port, start command and health check path are passed to the deploy.

The build log shows each decision, for example:

ℹ️ No Dockerfile found. Using Helipack builder
🔍 Detecting runtime... bun / nextjs (bun) → port 3000
🔍 Found Next.js config: next.config.mjs
✨ Injected standalone mode into next.config.mjs

Supported stacks

RuntimeFrameworksPackage managers
Node.jsNext.js, Nuxt, Remix, Astro, NestJS, Elysia, Hono, Express, Fastify, Vitenpm, yarn, pnpm
BunSame as Node.jsbun
PHPLaravel, Symfony, WordPress, CodeIgniter, Slim, Lumen, Yii, CakePHPcomposer
PythonDjango, FastAPI, Flask, Starlettepip, poetry, uv

Anything else: add a Dockerfile and Helipod builds it directly.

Configuration

Helipack works with no configuration. To override what it detects, add a helipack.json next to your package.json (or at the root of the service's rootDirectory):

helipack.json
{
  "run": {
    "port": 3000,
    "command": "node server.js"
  },
  "build": {
    "command": "npm run build:prod",
    "dockerfile": "Dockerfile.production",
    "strict": false
  },
  "health": {
    "path": "/health",
    "duration": 15
  },
  "packages": {
    "apk": ["openssl", "ffmpeg"]
  },
  "env": {
    "NODE_ENV": "production"
  }
}

Prop

Type

railway.json and railway.toml are read too, so a project that already deploys on Railway needs no changes: build.buildCommand, build.dockerfilePath, build.builder: "DOCKERFILE", build.watchPatterns, deploy.startCommand and deploy.healthcheckPath are understood.

Precedence (highest first): helipack.json, then railway.json / railway.toml, then the service settings (start command, health check, root directory), then detection. Configuration in the repository wins, so the same commit always builds the same way.

Commands must be one line (chain with &&) and paths must stay inside the repository; invalid values are skipped with a warning in the build log.

Variables and secrets during the build

  • Service variables are available while building, as a .env file mounted for the build only. Frameworks that read variables at build time (NEXT_PUBLIC_*, VITE_*, ...) see them. They are never written into an image layer.
  • Secret files added to the service in the dashboard are mounted for the build too, and removed afterwards.
  • .env files, private keys and .git in your repository are always excluded from the build context.

Next.js

The final image runs Next.js in standalone mode (a small server with only the files it needs, about 150 MB instead of well over 1 GB). Helipack adds output: "standalone" to your config for you:

  • it finds the object your config exports, also when it is wrapped by plugins, for example const config = {...}; export default withMDX(config);
  • if your config has a shape it can't edit safely (a function, a computed export), it keeps your file as next.config.helipack-original.* and adds a small wrapper that loads it and adds the option.

To skip the edit, set it yourself:

next.config.mjs
const config = {
  output: "standalone",
};
export default config;

--turbopack is removed from the build script, because the standalone output needs the regular build.

Monorepos

Set the service's root directory to the app's folder (apps/web, services/api) when you create it (source.rootDirectory) or later with PATCH /services/{id}. Only that folder is built. When a build at the repository root fails and the repository looks like a monorepo, the build log lists the app folders it found.

When a build fails

Read the deploy log: GET /services/{id}/deployments/{deploymentId}/logs. Common fixes:

Log saysDo this
No framework detectedAdd a Dockerfile, or set build.command and run.command in helipack.json
Wrong port, health check times outSet run.port in helipack.json, or make the app listen on $PORT
A system library is missing (libvips, openssl, ...)Add it under packages in helipack.json
TypeScript errors stop the buildThey only do when build.strict is true; fix them or turn it off
Monorepo built from the wrong folderSet the root directory

A failed build is refunded and the previous deployment keeps serving.

On this page