dockerize

Create a Dockerfile for the current project to deploy with haloy. Use when the user says "dockerize", "create a Dockerfile", "containerize my app", "make this deployable", or "prepare for haloy". This skill creates optimized, production-ready Dockerfiles without docker-compose. Not for multi-container setups requiring docker-compose, local development environments with multiple services, or Kubernetes manifests.

haloydev/agent-skills49 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: dockerize
description: Create a Dockerfile for the current project to deploy with haloy. Use when the user says "dockerize", "create a Dockerfile", "containerize my app", "make this deployable", or "prepare for haloy". This skill creates optimized, production-ready Dockerfiles without docker-compose. Not for multi-container setups requiring docker-compose, local development environments with multiple services, or Kubernetes manifests.
license: MIT
---

# Dockerize for Haloy

Create production-ready Dockerfiles optimized for deployment with [haloy](https://haloy.dev).

If the user's request involves docker-compose, multiple containers, or complex orchestration, inform them:

> "This skill creates single Dockerfiles for haloy deployment. Haloy works like docker-compose but for production, handling orchestration through its own configuration. If you need multiple services, each should have its own Dockerfile and be defined as separate services in your `haloy.yaml`. Would you like me to create a Dockerfile for a specific service instead?"

## How It Works

1. **Detect the project type** by examining:
   - `package.json` with `@tanstack/react-start` (TanStack Start)
   - `package.json` with `next` (Next.js)
   - `package.json` (Node.js/JavaScript/TypeScript)
   - `bun.lockb`, `pnpm-lock.yaml`, `package-lock.json`, or `yarn.lock` (Node package manager)
   - `requirements.txt`, `pyproject.toml`, `Pipfile` (Python)
   - `uv.lock`, `poetry.lock`, or `[tool.poetry]` in `pyproject.toml` (Python package manager)
   - `go.mod` (Go)
   - `Cargo.toml` (Rust)
   - `Gemfile` (Ruby)
   - `pom.xml`, `build.gradle` (Java)
   - `composer.json` (PHP)
   - Other indicators

2. **Analyze the application** to determine:
   - Build process and dependencies
   - Runtime requirements
   - Entry point / start command
   - Required environment variables
   - Static assets or build outputs

3. **Check for health endpoint** and ask user about creating one if missing (see Health Check section below)

4. **Create an optimized Dockerfile** following best practices:
   - Multi-stage builds to reduce image size
   - Appropriate base images (Alpine when possible)
   - Proper layer ordering for cache efficiency
   - Non-root user for security
   - HEALTHCHECK instruction pointing to the health endpoint

5. **Provide haloy configuration guidance** if no `haloy.yaml` exists

## Health Check Endpoint

A `/health` endpoint is strongly recommended for all haloy deployments. Before creating the Dockerfile, check if the application has an existing health endpoint.

### Why Health Checks Matter

Haloy uses health checks to:
- **Zero-downtime deployments**: New containers must pass health checks before receiving traffic
- **Auto-recovery**: Unhealthy containers are automatically restarted
- **Deployment validation**: Deployments fail fast if the app cannot start properly

Without a health check, haloy cannot verify your application is actually working, which can lead to routing traffic to broken containers.

### Check for Existing Health Endpoint

Search for existing health endpoints:
- `/health`, `/healthz`, `/api/health`, `/_health`
- Look for route definitions returning status 200 or `{ status: "ok" }`

### Ask the User

If no health endpoint exists, ask the user:

> "Your application doesn't appear to have a health check endpoint. Haloy uses health checks for zero-downtime deployments and auto-recovery. Would you like me to create a `/health` endpoint?"

If the user agrees, create a minimal health endpoint appropriate for the framework:

**TanStack Start** (`src/routes/health.tsx`):
```tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/health")({
  server: {
    handlers: {
      GET: async () => {
        return Response.json({ status: "ok" });
      },
    },
  },
});
```

**Express/Node.js**:
```javascript
app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});
```

**Next.js** (`app/health/route.ts` or `pages/api/health.ts`):
```typescript
export async function GET() {
  return Response.json({ status: 'ok' });
}
```

**FastAPI**:
```python
@app.get("/health")
def health():
    return {"status": "ok"}
```

**Go (net/http)**:
```go
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.Write([]byte(`{"status":"ok"}`))
})
```

### Health Check Best Practices

- Return quickly (avoid database queries in the basic health check)
- Return HTTP 200 for healthy, non-200 for unhealthy
- Keep the response minimal: `{"status": "ok"}` is sufficient
- Place at a consistent path: `/health` is the convention

## Decision Flow

Use defaults unless a critical value is missing. Only ask the user when needed.

- **Health endpoint**: Ask only if no existing health route is found.
- **Start command**: Use existing `scripts.start` or framework default. Ask if no clear entry point exists.
- **Port**: Use common defaults (3000 for Node, 8000 for Python) unless a config file or env var specifies a port.
- **Package manager**: Infer from lockfiles. If none exist, default to npm for Node and pip for Python.

## Base Image Versions

**IMPORTANT**: Always detect the local runtime version to ensure dev/prod consistency. Do not rely on your training data for version numbers.

**Version selection priority:**
1. **Project config files** (highest priority) - `.nvmrc`, `.node-version`, `engines.node`, `.python-version`, `go.mod`, `rust-toolchain.toml`, `.ruby-version`, etc.
2. **Local installed version** - run the runtime's version command
3. **Fallback to `references/base-images.md`** - only if above methods fail

See `references/base-images.md` for the full version detection guide, config file locations, version-to-tag mappings, and current recommended fallback versions.

Use `slim` variants by default, `alpine` for smaller images (some compatibility tradeoffs). Always use specific major.minor tags, never `latest`.

## Dockerfile Best Practices

### General Principles

- Use specific version tags, not `latest` (see Base Image Versions above)
- Minimize layers by combining RUN commands
- Order instructions from least to most frequently changing
- Use `.dockerignore` to exclude unnecessary files
- Run as non-root user in production
- Include EXPOSE for documentation

### Multi-stage Build Pattern

```dockerfile
# Build stage (verify current LTS version in references/base-images.md)
FROM node:24-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN npm run build

# Production stage
FROM node:24-slim AS runner
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && adduser -u 1001 -S appuser -G appgroup
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
```

### Framework-Specific Considerations

**TanStack Start**: See `references/tanstack-start.md` for detailed instructions. Key points:
- Requires `"type": "module"` in package.json
- Requires `vite.config.ts` with `tanstackStart()` and `nitro()` plugins
- Build output goes to `.output/server/index.mjs`
- Use Node 24 slim with pnpm via corepack
- Include a `/health` route for health checks

**Next.js**: Use standalone output mode, copy `.next/standalone` and `.next/static`

**Vite/React**: Build static files, serve with nginx or a Node server

**Python/FastAPI**: Use slim images, install with `--no-cache-dir`

**Go**: Build static binary, use scratch or distroless for minimal image

## Output Format

After creating the Dockerfile, provide output in this order:

1. The complete Dockerfile
2. A `.dockerignore` file if one doesn't exist
3. Instructions to build and test locally:
   ```bash
   docker build -t myapp .
   docker run -p 3000:3000 myapp
   ```
4. If no `haloy.yaml` exists, advise the user:
   > "To complete your haloy setup, you'll need a `haloy.yaml` configuration file. You can either run the `/haloy-config` skill to generate one, or check the [haloy documentation](https://haloy.dev/docs) for configuration options."

## Reference Files

Read the appropriate reference file for detailed instructions:

- **Base Images**: `references/base-images.md` - Current recommended versions for all runtimes (check this first!)
- **TanStack Start**: `references/tanstack-start.md` - Complete guide including health checks, database setup, and haloy.yaml examples

## When to Create .dockerignore

Always check for an existing `.dockerignore`. If missing, create one appropriate for the project type. Common exclusions:

```
node_modules
.git
.env
.env.*
*.log
dist
.next
.output
.vinxi
__pycache__
*.pyc
.venv
target
haloy.yaml
```

**Important**: Always include `haloy.yaml` in `.dockerignore`. This file is used by haloy for deployment configuration but is not needed inside the container. Excluding it improves Docker layer caching since changes to `haloy.yaml` (like updating domains or environment variables) won't invalidate the build cache.

More DevOps & Infrastructure skills

← All DevOps & Infrastructure skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY