---
url: https://migetpacks.com/configuration/app-json
---
---
title: "app.json"
description: "Application manifest support"
---
migetpacks supports `app.json` as an application manifest file. This file defines environment variables, process formation, deployment scripts, and buildpack configuration for your application.
## Supported Fields
| Field | Description |
|-------|-------------|
| `env` | Environment variables for build and runtime |
| `formation` | Process configuration with replicas and sizing |
| `scripts` | Deployment lifecycle scripts |
| `buildpacks` | Buildpack (language) ordering |
| `addons` | Service dependencies (databases, caches, etc.) |
## Environment Variables
The `env` field defines environment variables that are exported during the build and included in the [result JSON](/reference/result-json). Each variable can specify a fixed value, a generator, or be marked as required.
```json
{
"env": {
"RAILS_ENV": {
"value": "production"
},
"SECRET_KEY_BASE": {
"generator": "secret"
},
"DATABASE_URL": {
"required": true
},
"OPTIONAL_VAR": {
"description": "An optional config variable"
}
}
}
```
### Variable Properties
| Property | Description |
|----------|-------------|
| `value` | A fixed value for the variable. Exported during build and included in result JSON. |
| `generator` | Set to `"secret"` to auto-generate a random secret value. The generated value appears in the result JSON. |
| `required` | Set to `true` to indicate this variable must be provided at deploy time. Appears in result JSON with a `null` value. |
| `description` | Human-readable description (informational only, not used during build). |
## Formation
The `formation` field defines process types with their replica count and compute size. This information is passed through to the result JSON for use by deployment orchestrators.
```json
{
"formation": {
"web": {
"quantity": 2,
"size": "standard-1x"
},
"worker": {
"quantity": 1,
"size": "standard-2x"
},
"clock": {
"quantity": 1
}
}
}
```
### Formation Properties
| Property | Description |
|----------|-------------|
| `quantity` | Number of replicas (instances) for this process type |
| `size` | Compute size identifier (e.g., `standard-1x`, `performance-m`) |
## Scripts
The `scripts` field defines lifecycle hooks that run at specific points during deployment. These are included in the result JSON for the deployment system to execute.
```json
{
"scripts": {
"postdeploy": "bundle exec rails db:migrate",
"pr-predestroy": "bundle exec rails db:drop"
}
}
```
### Supported Scripts
| Script | When It Runs |
|--------|-------------|
| `postdeploy` | After a successful deployment (e.g., database migrations) |
| `pr-predestroy` | Before a review app is destroyed (e.g., cleanup) |
## Buildpacks
The `buildpacks` field maps to migetpacks language support. Heroku-style buildpack URLs are automatically translated to migetpacks language identifiers.
```json
{
"buildpacks": [
{"url": "heroku/nodejs"},
{"url": "heroku/ruby"}
]
}
```
### Buildpack URL Mapping
| Buildpack URL | migetpacks Language |
|---------------|-------------------|
| `heroku/nodejs` | `nodejs` |
| `heroku/ruby` | `ruby` |
| `heroku/python` | `python` |
| `heroku/go` | `go` |
| `heroku/java` | `java` |
| `heroku/php` | `php` |
| `heroku/scala` | `scala` |
| `heroku/clojure` | `clojure` |
When multiple buildpacks are specified, the order defines the build sequence. The first buildpack becomes the primary language (determines the runtime base image), and subsequent buildpacks provide additional build stages.
The `buildpacks` field in `app.json` is equivalent to setting the `BUILDPACKS` environment variable. If both are provided, the `BUILDPACKS` env var takes precedence.
## Addons
The `addons` field declares service dependencies for your application. Each addon can be specified as a simple string or as an object with additional configuration.
### String Format
```json
{
"addons": [
"heroku-postgresql:hobby-dev",
"heroku-redis:premium-0"
]
}
```
### Object Format
```json
{
"addons": [
{
"plan": "heroku-postgresql:standard-0",
"as": "PRIMARY_DB"
},
{
"plan": "mongolab:shared-single-small",
"as": "MONGO",
"options": {
"version": "5.0"
}
}
]
}
```
### Addon Properties
| Property | Description |
|----------|-------------|
| `plan` | The addon plan identifier (e.g., `heroku-postgresql:hobby-dev`) |
| `as` | Attachment alias — overrides the default config var prefix |
| `options` | Provider-specific configuration options |
String-format addons are normalized to object format in the result JSON with `as` and `options` set to `null`.
## Full Example
```json
{
"name": "my-rails-app",
"env": {
"RAILS_ENV": {
"value": "production"
},
"SECRET_KEY_BASE": {
"generator": "secret"
},
"DATABASE_URL": {
"required": true
},
"REDIS_URL": {
"required": true
},
"RAILS_LOG_TO_STDOUT": {
"value": "enabled"
}
},
"formation": {
"web": {
"quantity": 2,
"size": "standard-1x"
},
"worker": {
"quantity": 1,
"size": "standard-2x"
}
},
"scripts": {
"postdeploy": "bundle exec rails db:migrate"
},
"buildpacks": [
{"url": "heroku/nodejs"},
{"url": "heroku/ruby"}
],
"addons": [
"heroku-postgresql:hobby-dev",
{
"plan": "heroku-redis:premium-0",
"as": "CACHE"
}
]
}
```
## Result JSON Output
Values defined in `app.json` appear in the [build result JSON](/reference/result-json) output. Here is how each field maps:
```json
{
"status": "success",
"images": [
{"name": "registry.io/my-app:latest", "ports": ["5000/tcp"]}
],
"processes": {
"web": "bundle exec puma -C config/puma.rb",
"release": "bundle exec rails db:migrate",
"workers": [
{"name": "sidekiq", "command": "bundle exec sidekiq"}
]
},
"env": {
"RAILS_ENV": "production",
"SECRET_KEY_BASE": "a1b2c3d4e5f6...",
"DATABASE_URL": null,
"REDIS_URL": null,
"RAILS_LOG_TO_STDOUT": "enabled"
},
"formation": [
{"name": "web", "quantity": 2, "size": "standard-1x"},
{"name": "worker", "quantity": 1, "size": "standard-2x"}
],
"scripts": [
{"name": "postdeploy", "command": "bundle exec rails db:migrate"}
],
"addons": [
{"plan": "heroku-postgresql:hobby-dev", "as": null, "options": null},
{"plan": "heroku-redis:premium-0", "as": "CACHE", "options": null}
],
"language": "ruby",
"timestamp": "2025-12-18T20:00:00Z"
}
```
Variables with `"generator": "secret"` will have their generated value populated in the `env` section of the result JSON. Variables with `"required": true` appear with a `null` value, indicating they must be supplied by the deployment system.
---
url: https://migetpacks.com/configuration/build-metadata
---
---
title: "Build Metadata"
description: "Git provenance and builder facts emitted on every build"
---
## Overview
migetpacks records who built an image and from which commit, then exposes that
on four surfaces:
- The `build` block in the [result JSON](/reference/result-json).
- OCI image labels you can read with `docker inspect` without running the image.
- An in-image file `/.miget/build.json` (buildpack builds).
- `MIGET_*` build args for custom Dockerfile and Compose builds.
## Sources
Each field resolves by precedence, highest first:
1. The `build-meta` input envelope (set by the platform).
2. `MIGET_GIT_*` or `SOURCE_VERSION` environment variables, for standalone
`docker run` or CI builds.
3. `.git` autodetection of the source directory.
Unknown fields are omitted rather than set to empty strings.
## OCI labels
| Label | Value |
|-------|-------|
| `org.opencontainers.image.revision` | Full commit SHA |
| `org.opencontainers.image.source` | Repository URL |
| `org.opencontainers.image.created` | Build time (ISO 8601) |
| `com.miget.git.branch` | Branch |
| `com.miget.git.description` | Commit subject |
| `com.miget.builder.version` | migetpacks version |
```bash
docker inspect my-app:latest --format '{{json .Config.Labels}}'
```
## In-image file
Buildpack builds include `/.miget/build.json`, useful when you prefer a file
over environment variables or run a distroless image with no shell:
```bash
docker run --rm my-app:latest cat /.miget/build.json
```
## Custom Dockerfile and Compose builds
migetpacks does not modify a Dockerfile it does not own, so it passes the values
as build args. Opt in from your Dockerfile:
```dockerfile
ARG MIGET_GIT_COMMIT
ENV MIGET_GIT_COMMIT=${MIGET_GIT_COMMIT}
```
---
url: https://migetpacks.com/configuration/environment-variables
---
---
title: "Environment Variables"
description: "Complete reference for all configuration options"
---
## Builder Configuration
These environment variables control how migetpacks builds your application.
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `SOURCE_DIR` | No | `/workspace/source` | Source code directory |
| `OUTPUT_IMAGE` | Yes | - | Target image name (e.g., `registry.io/app:tag`) |
| `LANGUAGE` | No | auto-detected | Programming language override |
| `RUN_COMMAND` | No | from Procfile/default | Command to run the application |
| `PORT` | No | `5000` | Port the application listens on |
| `ARCH` | No | `x86_64` | Target architecture (`x86_64`, `arm64`) |
| `PROJECT_PATH` | No | - | Subdirectory within `SOURCE_DIR` for monorepo support |
| `DOCKERFILE_PATH` | No | - | Custom Dockerfile path (relative to `PROJECT_PATH` or absolute) |
| `COMPOSE_FILE` | No | - | Custom compose file path (auto-detects `compose.yaml`, `compose.yml`, `docker-compose.yaml`, `docker-compose.yml`) |
| `BUILDPACKS` | No | auto-detected | Explicit buildpack order (e.g., `ruby,python,nodejs` - first is primary) |
| `TAG_LATEST` | No | `false` | Also tag image with `:latest` in addition to primary tag |
| `RESULT_FILE` | No | - | Path to write build results JSON (for Shipwright post-build steps) |
| `STORAGE_DRIVER` | No | `overlay2` | Docker storage driver override (e.g., `fuse-overlayfs` for nested DinD) |
## Caching Options
These variables configure build caching for faster subsequent builds.
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `CACHE_IMAGE` | No | - | Registry image for BuildKit cache |
| `CACHE_REGISTRY_INSECURE` | No | `false` | Set to `true` for HTTP registries (e.g., internal cache registry) |
| `NO_CACHE` | No | `false` | Force fresh build (`--no-cache`), skips cache-from but still exports to cache-to |
| `CACHE_MODE` | No | `min` | BuildKit cache export mode: `min` (final layer only, smaller) or `max` (all layers, better hits) |
| `CACHE_FROM` | No | - | Additional read-only cache sources (comma-separated registry refs, skipped when `NO_CACHE=true`) |
| `REGISTRY_MIRROR` | No | - | Docker registry mirror URL (e.g., `https://registry.example.io/mirror`) |
| `BUILD_CACHE_DIR` | No | - | Shared cache directory for package managers (mount RWX volume here) |
## Docker Hardened Images
These variables enable Docker Hardened Images for secure, minimal containers.
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `USE_DHI` | No | `false` | Use Docker Hardened Images (dhi.io) for secure, minimal runtime containers |
| `DHI_USERNAME` | No | - | DHI registry username (alternative to mounting docker config) |
| `DHI_PASSWORD` | No | - | DHI registry password/token |
| `DHI_MIRROR` | No | - | DHI registry mirror URL (e.g., `https://registry.example.io/dhi-io`) |
## External Private Registry
Set these variables when your `Dockerfile` pulls a base image from a private registry (any `FROM /...` line). The builder runs `docker login` before invoking the build, so subsequent pulls authenticate. Values may come from the environment directly or from the platform's `BUILD_VARS` JSON envelope.
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `EXTERNAL_REGISTRY_URL` | No | - | Registry host (e.g., `docker.io`, `ghcr.io`, `registry.digitalocean.com`, `.dkr.ecr..amazonaws.com`). No protocol prefix. |
| `EXTERNAL_REGISTRY_USERNAME` | No | - | Username (or token name where the provider uses it as the username, e.g., `AWS` for ECR). |
| `EXTERNAL_REGISTRY_PASSWORD` | No | - | Password or read-only access token. Read-only tokens are recommended. |
All three must be present for the login step to run. Login failure is logged and non-fatal — the FROM-pull error then surfaces during `docker build`, matching the DHI block's behaviour.
### Example
```bash docker
docker run --rm \
-v "$(pwd):/workspace/source:ro" \
-v /var/run/docker.sock:/var/run/docker.sock \
-e EXTERNAL_REGISTRY_URL=docker.io \
-e EXTERNAL_REGISTRY_USERNAME=my-user \
-e EXTERNAL_REGISTRY_PASSWORD=$DOCKERHUB_TOKEN \
-e OUTPUT_IMAGE=myapp:latest \
miget/migetpacks:latest-alpine
```
## Custom Environment Variables
For build provenance variables (`MIGET_GIT_COMMIT`, etc.) see [Build Metadata](/configuration/build-metadata).
Any environment variable passed to migetpacks that is **not** in the known builder variables list is automatically injected into the generated Dockerfile as an `ENV` statement. This allows you to configure your application's build environment without modifying any configuration files.
### How It Works
1. You pass an environment variable to the migetpacks container (e.g., `-e NODE_OPTIONS="--max-old-space-size=4096"`)
2. The builder checks whether it matches any known builder variable pattern
3. If it does not match, it is added to the generated Dockerfile as an `ENV` instruction
4. The variable becomes available during both the build and runtime stages
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-e OUTPUT_IMAGE=my-app:latest \
-e NODE_OPTIONS="--max-old-space-size=4096" \
-e VITE_API_URL="https://api.example.com" \
miget/migetpacks:latest
```
The generated Dockerfile will include:
```dockerfile
# Custom build environment variables
ENV NODE_OPTIONS="--max-old-space-size=4096"
ENV VITE_API_URL="https://api.example.com"
```
### Common Use Cases
Increase the V8 heap size for large builds that run out of memory:
```bash
-e NODE_OPTIONS="--max-old-space-size=4096"
```
Inject variables consumed by Vite, Next.js, or similar frameworks during the build:
```bash
-e VITE_API_URL="https://api.example.com"
-e NEXT_PUBLIC_ANALYTICS_ID="UA-123456"
```
Provide the master key so Rails can decrypt credentials during asset precompilation:
```bash
-e RAILS_MASTER_KEY="abc123def456"
```
Pass arbitrary flags to your build tooling:
```bash
-e CARGO_BUILD_JOBS="4"
-e MIX_ENV="prod"
-e BUNDLE_DEPLOYMENT="true"
```
### Filtered Variables
The following categories of variables are **never** passed through to the Dockerfile:
- **Builder config**: `SOURCE_DIR`, `OUTPUT_IMAGE`, `LANGUAGE`, `BUILD_COMMAND`, `RUN_COMMAND`, `PORT`, `ARCH`, `CACHE_IMAGE`, `NO_CACHE`, `CACHE_MODE`, `CACHE_FROM`, `BUILDPACKS`, `USE_DHI`, `REGISTRY_MIRROR`, `RESULT_FILE`, `STORAGE_DRIVER`, etc.
- **Language-specific**: Go build flags, Scala/sbt options, Clojure/Leiningen settings
- **Credentials**: `AWS_*` variables (never exposed in images)
- **Docker/system**: `DOCKER_HOST`, `DOCKER_CONFIG`, `DOCKER_BUILDKIT`, etc.
- **Shell/system**: `PATH`, `HOME`, `PWD`, `TERM`, `SHELL`, `USER`, `HOSTNAME`, `LANG`, `LC_*`, `XDG_*`, `SSH_*`, etc.
### Build-arg name requirements
App environment variables reach the build through the platform's `BUILD_VARS` envelope and are projected as Docker build args (`--build-arg` flags and generated `ARG` lines). Build args — like shell variables and Dockerfile `ARG`/`ENV` keys — must be **POSIX identifiers** (IEEE Std 1003.1 §8.1): a letter or underscore followed by letters, digits, or underscores, i.e. `^[A-Za-z_][A-Za-z0-9_]*$`.
Keys that don't satisfy this rule — for example dotted settings such as `discovery.seed_hosts` (used by Elasticsearch and other JVM apps) — are **skipped from the build-arg projection and logged**. They are still delivered to the running container as ordinary environment variables; they simply cannot be build args, because `ARG discovery.seed_hosts` is not a valid Dockerfile instruction.
- **Runtime env** (Compose Specification, permissive): keys are preserved verbatim.
- **Build args** (POSIX / Docker `ARG`): only identifier-safe keys are projected; the rest are runtime-only.
If you need a non-identifier setting available **at build time**, expose it under an identifier-safe alias (e.g. `ES_DISCOVERY_SEED_HOSTS`) and reference that in your Dockerfile or build script.
---
url: https://migetpacks.com/configuration/version-detection
---
---
title: "Version Detection"
description: "How migetpacks detects language versions"
---
migetpacks automatically detects the language version for your project by checking environment variables, version files, and project configuration files in a defined priority order. If no version is found, a sensible default is used.
## Detection Priority by Language
Each language checks sources in the order listed below. The first match wins.
### Node.js
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `NODE_VERSION` env var | `NODE_VERSION=20.11.0` |
| 2 | `.node-version` file | `20.11.0` |
| 3 | `.nvmrc` file | `20` |
| 4 | `package.json` `engines.node` | `"node": ">=20"` |
| - | Default | `22.16.0` |
### Bun
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `BUN_VERSION` env var | `BUN_VERSION=1.1.0` |
| 2 | `.bun-version` file | `1.1.0` |
| 3 | `bunfig.toml` `version` field | `version = "1.1.0"` |
| - | Default | `1.3.4` |
### Deno
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `DENO_VERSION` env var | `DENO_VERSION=2.0.0` |
| 2 | `.deno-version` file | `2.0.0` |
| 3 | `.dvmrc` file | `2.0.0` |
| 4 | `deno.json` `version` field | `"version": "2.0.0"` |
| - | Default | `2.6.1` |
### Ruby
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `RUBY_VERSION` env var | `RUBY_VERSION=3.3.0` |
| 2 | `.ruby-version` file | `3.3.0` |
| 3 | `Gemfile` ruby directive | `ruby "3.3.0"` |
| - | Default | `3.4.4` |
### Python
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `PYTHON_VERSION` env var | `PYTHON_VERSION=3.12.0` |
| 2 | `.python-version` file | `3.12.0` |
| 3 | `runtime.txt` | `python-3.12.0` |
| 4 | `uv.lock` `requires-python` | `requires-python = ">=3.13"` |
| 5 | `pyproject.toml` `requires-python` | `requires-python = ">=3.12"` |
| - | Default | `3.12.8` |
### Go
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `GO_VERSION` env var | `GO_VERSION=1.22.0` |
| 2 | `.go-version` file | `1.22.0` |
| 3 | `go.mod` go directive | `go 1.22` |
| - | Default | `1.25.0` |
### Rust
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `RUSTUP_TOOLCHAIN` env var | `RUSTUP_TOOLCHAIN=1.80.0` |
| 2 | `rust-toolchain` file | `1.80.0` |
| 3 | `rust-toolchain.toml` channel | `channel = "1.80.0"` |
| - | Default | `1.92.0` |
### PHP
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `PHP_VERSION` env var | `PHP_VERSION=8.3` |
| 2 | `.php-version` file | `8.3` |
| 3 | `composer.json` `require.php` | `"php": ">=8.2"` |
| - | Default | `8.3` |
### Java
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `JAVA_VERSION` env var | `JAVA_VERSION=21` |
| 2 | `.java-version` file | `21` |
| 3 | `system.properties` | `java.runtime.version=21` |
| 4 | `pom.xml` properties | `21` |
| - | Default | `21` |
### Kotlin
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `KOTLIN_VERSION` env var | `KOTLIN_VERSION=2.0.0` |
| 2 | `.kotlin-version` file | `2.0.0` |
| 3 | `gradle.properties` | `kotlin.version=2.0.0` |
| 4 | `build.gradle.kts` plugin version | `kotlin("jvm") version "2.0.0"` |
| - | Default | `2.1.0` |
### Scala
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `SCALA_VERSION` env var | `SCALA_VERSION=3.4.0` |
| 2 | `.scala-version` file | `3.4.0` |
| 3 | `build.sbt` scalaVersion | `scalaVersion := "3.4.0"` |
| 4 | `project/build.properties` | `scala.version=3.4.0` |
| - | Default | `3.5.2` |
### Clojure
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `CLOJURE_VERSION` env var | `CLOJURE_VERSION=1.12.0` |
| 2 | `.clojure-version` file | `1.12.0` |
| 3 | `project.clj` dependency | `[org.clojure/clojure "1.12.0"]` |
| 4 | `deps.edn` dependency | `org.clojure/clojure {:mvn/version "1.12.0"}` |
| - | Default | `1.11.1` |
### .NET
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `DOTNET_VERSION` env var | `DOTNET_VERSION=8.0` |
| 2 | `global.json` SDK version | `"version": "8.0.100"` |
| 3 | `*.csproj` TargetFramework | `net8.0` |
| - | Default | `8.0` |
### Elixir
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `ELIXIR_VERSION` env var | `ELIXIR_VERSION=1.17.0` |
| 2 | `.elixir-version` file | `1.17.0` |
| 3 | `.tool-versions` (asdf) | `elixir 1.17.0` |
| - | Default | `1.18.4` |
## Version Normalization
migetpacks normalizes version constraints found in project files to Docker-compatible image tags. The following transformations are applied:
### Prefix Removal
The `v` prefix is stripped from versions:
```text
v20.11.0 -> 20.11.0
```
### Semver Range Operators
Operators like `>=`, `^`, `~`, `>`, `<`, and `=` are removed, and the base version is extracted:
```text
>=18 -> 18
^20.0.0 -> 20.0
~20.0.0 -> 20.0
>18 -> 18
```
For full semver versions with operators, the patch version is dropped:
```text
^20.11.1 -> 20.11
~3.12.0 -> 3.12
```
### Wildcard Patterns
The `.x` and `.*` patterns are reduced to the major version:
```text
20.x -> 20
22.* -> 22
```
### OR Alternatives
When a version string contains `||` separated alternatives, the highest major version is selected:
```text
20.x || 22.x || 24.x -> 24
>=18 || >=20 -> 20
```
### Plain Versions
Explicit versions without operators are kept as-is, since Docker Hub has tags for all common formats:
```text
20 -> 20
20.11 -> 20.11
20.11.0 -> 20.11.0
3.12.8 -> 3.12.8
```
---
url: https://migetpacks.com/contributing
---
---
title: "Contributing"
description: "How to develop, test, and contribute to migetpacks."
---
migetpacks is open source and available on [GitHub](https://github.com/migetapp/migetpacks). Contributions are welcome under the [O'Saasy License](https://github.com/migetapp/migetpacks/blob/main/LICENSE).
## Prerequisites
- Docker (with BuildKit support)
- Bash 4+
- `make` (optional, for build shortcuts)
## Project Structure
```text
bin/
├── detect # Language detection script
├── detect-version # Version detection script
├── build # Main build orchestrator
├── release # Release metadata (Procfile parsing)
└── entrypoint.sh # Container entrypoint (starts dockerd)
lib/
├── common.sh # Shared helpers (output formatting, cache IDs)
├── buildpacks.sh # Multi-buildpack support
├── nodejs.sh # Node.js/npm/yarn/pnpm
├── python.sh # Python/pip/uv
├── ruby.sh # Ruby/Bundler/Rails
├── go.sh # Go modules
├── rust.sh # Rust/Cargo
├── java.sh # Java/Maven/Gradle
├── kotlin.sh # Kotlin/Gradle
├── scala.sh # Scala/sbt
├── clojure.sh # Clojure/Leiningen
├── dotnet.sh # .NET/C#
├── php.sh # PHP/Composer/FrankenPHP
├── elixir.sh # Elixir/Phoenix
├── deno.sh # Deno
└── bun.sh # Bun
examples/ # Working example apps for each language
test/ # Test scripts
```
## Development Commands
```bash
# Test the detect scripts
./test/test-detect.sh
# Detect language for an example app
./bin/detect examples/nodejs-example
# Detect version for an example app
LANGUAGE=nodejs ./bin/detect-version examples/nodejs-example
# Build the builder container
make build
# Push to registry
make push
```
## Testing Locally
You can test builds locally without pushing to a remote registry:
```bash
# Build the migetpacks container
docker build -t migetpacks:local .
# Run a local build (image stays local, won't push)
docker run --rm \
-v /path/to/your/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
migetpacks:local
# Test the built image
docker run --rm -p 5000:5000 my-app:local
```
When `OUTPUT_IMAGE` does not contain a registry prefix (e.g., `my-app:local` vs `registry.io/my-app:latest`), the builder uses the local Docker daemon and the image is not pushed.
### Debugging Tips
- Check the generated Dockerfile in the build output (`Dockerfile.runtime`)
- Inspect the built image: `docker run --rm my-app:local ls -la /app/`
- Check environment: `docker run --rm my-app:local env`
## Example Apps
Working examples for all supported languages are in `examples/`:
| Directory | Language |
|-----------|----------|
| `nodejs-example` | Node.js |
| `deno-example` | Deno |
| `bun-example` | Bun |
| `python-example` | Python |
| `ruby-example` | Ruby |
| `go-hello-world` | Go |
| `rust-example` | Rust |
| `php-hello-world` | PHP |
| `java-hello-world` | Java |
| `kotlin-example` | Kotlin |
| `scala-example` | Scala |
| `dotnet-hello-world` | .NET |
| `elixir-example` | Elixir |
| `dockerfile-example` | Custom Dockerfile |
| `compose-example` | Docker Compose |
## Adding a New Language
To add support for a new language:
1. Add a detection function in `bin/detect`
2. Add a version detection function in `bin/detect-version`
3. Create a `lib/{language}.sh` with `{language}_get_images()`, `{language}_generate_builder()`, and `{language}_generate_runtime()` functions
4. Wire the new language into `bin/build`
5. Add an example app in `examples/`
6. Add tests in `test/`
---
url: https://migetpacks.com/deploying/kubernetes
---
---
title: "Kubernetes"
description: "Running migetpacks as Kubernetes Jobs"
---
## Overview
You can run migetpacks as a Kubernetes Job for one-off or scheduled builds without needing the full Shipwright framework. This approach gives you direct control over the build process while still leveraging Kubernetes for orchestration.
## Kubernetes Job
A basic Job that builds and pushes a container image:
```yaml
apiVersion: batch/v1
kind: Job
metadata:
name: build-my-app
namespace: builds
spec:
backoffLimit: 0 # Don't retry failed builds
activeDeadlineSeconds: 900 # 15 minute timeout
template:
spec:
restartPolicy: Never
containers:
- name: build
image: miget/migetpacks:latest
securityContext:
privileged: true # Required for Docker-in-Docker
env:
- name: OUTPUT_IMAGE
value: registry.io/your-org/your-app:latest
- name: LANGUAGE
value: nodejs
volumeMounts:
- name: source
mountPath: /workspace/source
readOnly: true
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "4"
memory: "8Gi"
volumes:
- name: source
persistentVolumeClaim:
claimName: app-source # PVC with your source code
```
The `privileged: true` security context is required because migetpacks uses Docker-in-Docker (DinD) internally to build images.
## Build Cache with PVC
Use a ReadWriteMany (RWX) PersistentVolumeClaim to share package manager caches across builds. This significantly speeds up dependency installation for repeated builds.
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: build-cache
namespace: builds
spec:
accessModes:
- ReadWriteMany # RWX required for concurrent builds
resources:
requests:
storage: 20Gi
storageClassName: your-rwx-storage-class # e.g., nfs, cephfs, efs
```
Mount it in your Job:
```yaml
volumeMounts:
- name: build-cache
mountPath: /cache
env:
- name: BUILD_CACHE_DIR
value: /cache
volumes:
- name: build-cache
persistentVolumeClaim:
claimName: build-cache
```
The cache stores package manager artifacts per language:
| Language | Cache Path | Contents |
|----------|-----------|----------|
| Node.js | `/cache/npm`, `/cache/yarn`, `/cache/pnpm` | Package tarballs |
| Python | `/cache/pip`, `/cache/uv` | Wheel files |
| Ruby | `/cache/bundler` | Gem files |
| Go | `/cache/go` | Module sources |
| Rust | `/cache/cargo` | Crate sources |
| Java | `/cache/maven`, `/cache/gradle` | Dependencies |
| PHP | `/cache/composer` | Composer packages |
## ConfigMap for Environment Variables
Use a ConfigMap to manage build configuration separately from the Job definition:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: build-config
namespace: builds
data:
PORT: "3000"
USE_DHI: "true"
REGISTRY_MIRROR: "https://registry.example.io/mirror"
CACHE_MODE: "max"
NODE_OPTIONS: "--max-old-space-size=4096"
```
Reference it in your Job:
```yaml
containers:
- name: build
image: miget/migetpacks:latest
envFrom:
- configMapRef:
name: build-config
env:
- name: OUTPUT_IMAGE
value: registry.io/your-org/your-app:latest
```
## Secret for Registry Credentials
Store registry credentials in a Kubernetes Secret:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: registry-credentials
namespace: builds
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson:
```
Mount it as a Docker config file:
```yaml
containers:
- name: build
image: miget/migetpacks:latest
env:
- name: DOCKER_CONFIG
value: /docker-config
volumeMounts:
- name: docker-config
mountPath: /docker-config
volumes:
- name: docker-config
secret:
secretName: registry-credentials
items:
- key: .dockerconfigjson
path: config.json
```
## RESULT_FILE with Shared Volume
Use an `emptyDir` volume to share build results between the builder and a post-build notification container:
```yaml
volumes:
- name: results
emptyDir: {}
```
Set the `RESULT_FILE` environment variable to write results to this volume:
```yaml
env:
- name: RESULT_FILE
value: /workspace/output/build-result.json
volumeMounts:
- name: results
mountPath: /workspace/output
```
The builder always exits with code 0, writing the build status to the result file. Post-build containers should check the `status` field to determine success or failure.
## Post-Build Notification
Use an init container pattern or a sidecar to process build results. Here is an example using a second container that waits for the result file:
```yaml
containers:
- name: build
image: miget/migetpacks:latest
# ... build configuration ...
- name: notify
image: your-rabbitmq-publisher:latest
command: ["/bin/sh", "-c"]
args:
- |
echo "Waiting for build result..."
while [ ! -f /workspace/output/build-result.json ]; do
sleep 2
done
echo "Build complete, publishing result..."
/app/publish-result /workspace/output/build-result.json
env:
- name: RABBITMQ_URL
valueFrom:
secretKeyRef:
name: rabbitmq-credentials
key: url
- name: RABBITMQ_QUEUE
value: build-results
volumeMounts:
- name: results
mountPath: /workspace/output
```
## Full Example
A complete Job definition with all components -- source, cache, registry credentials, configuration, and post-build notification:
```yaml
apiVersion: batch/v1
kind: Job
metadata:
name: build-my-app
namespace: builds
spec:
backoffLimit: 0
activeDeadlineSeconds: 900
template:
spec:
restartPolicy: Never
containers:
- name: build
image: miget/migetpacks:latest
securityContext:
privileged: true
envFrom:
- configMapRef:
name: build-config
env:
- name: OUTPUT_IMAGE
value: registry.io/your-org/your-app:latest
- name: RESULT_FILE
value: /workspace/output/build-result.json
- name: BUILD_CACHE_DIR
value: /cache
- name: CACHE_IMAGE
value: registry.io/your-org/your-app:cache
- name: DOCKER_CONFIG
value: /docker-config
volumeMounts:
- name: source
mountPath: /workspace/source
readOnly: true
- name: build-cache
mountPath: /cache
- name: results
mountPath: /workspace/output
- name: docker-config
mountPath: /docker-config
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "4"
memory: "8Gi"
- name: notify
image: your-rabbitmq-publisher:latest
command: ["/bin/sh", "-c"]
args:
- |
while [ ! -f /workspace/output/build-result.json ]; do sleep 2; done
/app/publish-result /workspace/output/build-result.json
env:
- name: RABBITMQ_URL
valueFrom:
secretKeyRef:
name: rabbitmq-credentials
key: url
volumeMounts:
- name: results
mountPath: /workspace/output
volumes:
- name: source
persistentVolumeClaim:
claimName: app-source
- name: build-cache
persistentVolumeClaim:
claimName: build-cache
- name: results
emptyDir: {}
- name: docker-config
secret:
secretName: registry-credentials
items:
- key: .dockerconfigjson
path: config.json
```
---
url: https://migetpacks.com/deploying/miget
---
---
title: "Miget"
description: "Deploy with migetpacks on the Miget platform"
---
## What is Miget?
[Miget](https://miget.com) is the platform that created and maintains migetpacks. When you deploy on Miget, migetpacks is natively integrated. Your code is automatically detected, built, and deployed without any configuration.
Miget lets you run unlimited applications on a single compute resource with a fair scheduler that intelligently distributes CPU across your workloads. Each app runs in an isolated microVM with managed databases, custom domains, and unmetered bandwidth included.
## How it works
Push your code to Miget and the platform automatically uses migetpacks to:
- Detect your language and version from source
- Generate an optimized multi-stage Dockerfile
- Build a secure, non-root container image
- Deploy it across Miget's infrastructure
No Dockerfile, no build configuration, and no CI pipeline to maintain.
## Why Miget
Run as many apps as you need on shared compute. No per-app billing, no surprise costs.
PostgreSQL, MySQL, and Valkey, provisioned in seconds alongside your apps.
Every app runs in its own secure VM with CIS and FIPS 140-2 compliance.
Deploy to EU and US regions today, with more locations coming soon.
## Getting started
Sign up at [miget.com](https://miget.com) and connect your repository. Miget handles the rest.
---
url: https://migetpacks.com/deploying/self-hosted-runners
---
---
title: "Self-Hosted Runners"
description: "Performance tips for CI/CD runners"
---
## Overview
Running migetpacks on self-hosted CI/CD runners (GitHub Actions, GitLab, etc.) gives you control over the build environment and enables persistent caching. However, storage architecture and configuration significantly impact build performance.
## Storage Architecture
The underlying storage for `/var/lib/docker` (where Docker stores layers and images) directly affects build speed.
### Local NVMe/SSD
Best performance. Docker layer operations (unpacking, caching, building) are I/O-intensive and benefit from low-latency local storage.
```text
Runner → Docker → ext4 → NVMe/SSD
```
### Distributed Storage (CephFS, NFS, EFS)
Slower due to the I/O path. Docker typically uses a loopback device on top of distributed filesystems, adding overhead:
```text
Runner → Docker → ext4 → loopback → CephFS → network → OSD
```
**Symptoms of slow distributed storage:**
- High `iowait` during layer unpacking (visible in `top` or `iostat`)
- First builds taking 5-10x longer than subsequent builds
- Slow `docker pull` operations
High iowait during layer unpacking is normal for distributed storage. This is not a migetpacks issue -- it is inherent to Docker's storage driver writing many small files through the network storage path.
### Storage Driver Options
migetpacks defaults to `overlay2` but supports alternative storage drivers via the `STORAGE_DRIVER` environment variable:
```bash
# For nested Docker-in-Docker on some storage backends
STORAGE_DRIVER=fuse-overlayfs
```
Use `fuse-overlayfs` when running nested DinD (e.g., migetpacks inside another Docker container) where `overlay2` is not available.
## Performance Tips
### 1. Pre-pull Base Images
Pull commonly used base images during runner initialization to avoid cold-start delays:
```bash
# Runner startup script
docker pull node:20-slim &
docker pull ruby:3.2-slim &
docker pull python:3.11-slim &
docker pull golang:1.22 &
wait
```
### 2. Use Registry Mirrors
Configure a pull-through cache registry (e.g., Harbor proxy cache) to avoid hitting Docker Hub rate limits and reduce pull times:
```bash
REGISTRY_MIRROR=https://registry.example.io/mirror
```
The mirror is configured in the Docker daemon's `registry-mirrors` setting inside migetpacks, so all image pulls automatically try the mirror first.
### 3. Persistent Cache Directory
Mount a persistent directory for package manager caches that survives runner restarts:
```bash
# Create persistent cache directory on the runner
mkdir -p /home/runner/migetpacks-cache
# Pass it to migetpacks
docker run --rm \
-v /home/runner/migetpacks-cache:/cache \
-e BUILD_CACHE_DIR=/cache \
miget/migetpacks:latest
```
### 4. Docker Layer Cache Persistence
Docker layer cache persists in `/var/lib/docker` between builds on the same runner. This means:
- Base images are pulled only once
- Unchanged layers from previous builds are reused
- BuildKit inline cache provides cross-build layer reuse
Ensure your runner does not prune Docker images between builds unless disk space is constrained.
### 5. Registry-Based BuildKit Cache
Use `CACHE_IMAGE` to store and retrieve BuildKit cache from a registry. This works across runners and survives runner reprovisioning:
```bash
CACHE_IMAGE=registry.io/your-org/your-app:cache
CACHE_MODE=max # Cache all intermediate layers (better hit rate)
```
## GitHub Actions Workflow
A complete workflow for self-hosted runners with persistent caching:
```yaml
name: Build with migetpacks
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Log into registry
uses: docker/login-action@v3
with:
registry: your-registry.io
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Setup cache directory
run: mkdir -p /home/runner/migetpacks-cache
- name: Build with migetpacks
run: |
docker run --rm \
-v ${{ github.workspace }}:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /home/runner/migetpacks-cache:/cache \
-e OUTPUT_IMAGE=your-registry.io/app:${{ github.sha }} \
-e BUILD_CACHE_DIR=/cache \
-e USE_DHI=true \
-e REGISTRY_MIRROR=https://registry.example.io/mirror \
-e CACHE_IMAGE=your-registry.io/app:cache \
-e CACHE_MODE=max \
miget/migetpacks:latest
- name: Tag latest (main only)
if: github.ref == 'refs/heads/main'
run: |
docker tag your-registry.io/app:${{ github.sha }} your-registry.io/app:latest
docker push your-registry.io/app:latest
```
## Build Time Expectations
| Scenario | First Build | Subsequent Builds |
|----------|------------|-------------------|
| Local NVMe, no cache | 30-120s | 10-30s |
| Local NVMe, with cache | 20-60s | 5-15s |
| CephFS, no cache | 60-300s | 20-60s |
| CephFS, with cache | 40-120s | 10-30s |
These times vary based on application size, dependency count, and network speed to registries.
## Troubleshooting
### High iowait during builds
This is normal for distributed storage. The Docker storage driver writes many small files during layer unpacking. Consider moving `/var/lib/docker` to local SSD if available.
### Docker Hub rate limits
Use `REGISTRY_MIRROR` to configure a pull-through cache, or authenticate with Docker Hub to increase rate limits:
```bash
docker login -u your-user -p your-token
```
### Out of disk space
Docker layer cache grows over time. Schedule periodic pruning on runners:
```bash
# Prune images older than 7 days (run during off-hours)
docker image prune -a --filter "until=168h" --force
```
### Slow first build after runner restart
The first build after a restart needs to pull base images and warm the layer cache. Use pre-pulling in your runner startup script to minimize this delay.
---
url: https://migetpacks.com/deploying/shipwright
---
---
title: "Shipwright"
description: "ClusterBuildStrategy for Kubernetes-native builds"
---
## What is Shipwright?
[Shipwright](https://shipwright.io) is a Kubernetes-native build framework that provides a declarative API for building container images. It abstracts away the complexity of container builds by defining **BuildStrategies** that encapsulate build tools and processes.
migetpacks integrates with Shipwright as a **ClusterBuildStrategy**, allowing you to build application containers directly from source code using Kubernetes-native resources.
## ClusterBuildStrategy
The ClusterBuildStrategy defines how migetpacks runs inside the cluster. It specifies the builder image, volumes, and parameters available to builds.
```yaml
apiVersion: shipwright.io/v1beta1
kind: ClusterBuildStrategy
metadata:
name: migetpacks
spec:
volumes:
- name: results
emptyDir: {}
- name: build-cache
persistentVolumeClaim:
claimName: build-cache # RWX PVC shared across builds
steps:
- name: build
image: miget/migetpacks:latest
securityContext:
privileged: true # Required for Docker-in-Docker
volumeMounts:
- name: results
mountPath: /workspace/output
- name: build-cache
mountPath: /cache
env:
- name: SOURCE_DIR
value: $(params.shp-source-root)
- name: OUTPUT_IMAGE
value: $(params.shp-output-image)
- name: RESULT_FILE
value: /workspace/output/build-result.json
- name: BUILD_CACHE_DIR
value: /cache
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
```
The `privileged: true` security context is required because migetpacks uses Docker-in-Docker (DinD) to build container images.
## Volumes
migetpacks uses several volume types in Shipwright builds:
| Volume | Type | Purpose |
|--------|------|---------|
| Source | Shipwright-managed | Source code from Git repository |
| Results | `emptyDir` | Build result JSON for post-build steps |
| Build Cache | PVC (RWX) | Shared package manager cache across builds |
## Environment Variables
Pass environment variables to customize the build behavior. These can be set directly in the BuildStrategy or passed through the Build resource.
```yaml
env:
- name: LANGUAGE
value: ruby
- name: PORT
value: "3000"
- name: USE_DHI
value: "true"
- name: REGISTRY_MIRROR
value: https://registry.example.io/mirror
- name: CACHE_IMAGE
value: registry.io/myapp:cache
```
Any environment variable not in the known builder variables list is automatically injected into the generated Dockerfile as an `ENV` statement. This is useful for passing build-time configuration like `NODE_OPTIONS` or `RAILS_MASTER_KEY`.
## RESULT_FILE for Post-Build Steps
When `RESULT_FILE` is set, migetpacks writes a JSON file containing build results. This enables post-build steps (like deployment notifications) to access build metadata.
The builder always exits with code 0 to ensure post-build steps execute. Check the `status` field in the result JSON to determine if the build succeeded or failed.
```yaml
volumes:
- name: results
emptyDir: {}
steps:
- name: build
image: miget/migetpacks:latest
volumeMounts:
- name: results
mountPath: /workspace/output
env:
- name: RESULT_FILE
value: /workspace/output/build-result.json
- name: notify
image: your-rabbitmq-publisher:latest
volumeMounts:
- name: results
mountPath: /workspace/output
# Reads /workspace/output/build-result.json and publishes to RabbitMQ
```
## Build Resource
The Build resource defines what to build and where to push the result.
```yaml
apiVersion: shipwright.io/v1beta1
kind: Build
metadata:
name: my-app
namespace: default
spec:
source:
type: Git
git:
url: https://github.com/your-org/your-app.git
revision: main
contextDir: . # Or a subdirectory for monorepos
strategy:
name: migetpacks
kind: ClusterBuildStrategy
output:
image: registry.io/your-org/your-app:latest
credentials:
name: registry-credentials # Secret with .dockerconfigjson
env:
- name: USE_DHI
value: "true"
- name: PORT
value: "3000"
timeout: 10m
```
## BuildRun Resource
A BuildRun triggers an actual build execution.
```yaml
apiVersion: shipwright.io/v1beta1
kind: BuildRun
metadata:
name: my-app-run-1
namespace: default
spec:
build:
name: my-app
env:
- name: NO_CACHE
value: "true" # Force fresh build for this run
serviceAccount:
name: build-sa
timeout: 15m
```
You can also create a BuildRun that overrides the output image:
```yaml
apiVersion: shipwright.io/v1beta1
kind: BuildRun
metadata:
generateName: my-app-run-
namespace: default
spec:
build:
name: my-app
output:
image: registry.io/your-org/your-app:$(git-sha)
credentials:
name: registry-credentials
```
## Full Example with Cache and Results
This complete example shows a ClusterBuildStrategy with build caching, registry mirror, and a post-build notification step:
```yaml
apiVersion: shipwright.io/v1beta1
kind: ClusterBuildStrategy
metadata:
name: migetpacks-full
spec:
volumes:
- name: results
emptyDir: {}
- name: build-cache
persistentVolumeClaim:
claimName: migetpacks-cache # RWX PVC
steps:
- name: build
image: miget/migetpacks:latest
securityContext:
privileged: true
volumeMounts:
- name: results
mountPath: /workspace/output
- name: build-cache
mountPath: /cache
env:
- name: SOURCE_DIR
value: $(params.shp-source-root)
- name: OUTPUT_IMAGE
value: $(params.shp-output-image)
- name: RESULT_FILE
value: /workspace/output/build-result.json
- name: BUILD_CACHE_DIR
value: /cache
- name: REGISTRY_MIRROR
value: https://registry.example.io/mirror
- name: USE_DHI
value: "true"
- name: CACHE_IMAGE
value: $(params.shp-output-image)-cache
- name: CACHE_MODE
value: max
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "4"
memory: "8Gi"
- name: notify
image: your-notification-service:latest
volumeMounts:
- name: results
mountPath: /workspace/output
env:
- name: RESULT_PATH
value: /workspace/output/build-result.json
- name: RABBITMQ_URL
valueFrom:
secretKeyRef:
name: rabbitmq-credentials
key: url
```
---
url: https://migetpacks.com/guides/build-caching
---
---
title: "Build Caching"
description: "Speed up builds with registry-based caching, shared package manager caches, and optimized Docker layer ordering."
---
## Overview
migetpacks supports three complementary caching strategies that work together to minimize build times:
1. **Registry-based BuildKit cache** (`CACHE_IMAGE`) - stores build layers in a container registry
2. **Shared package manager cache** (`BUILD_CACHE_DIR`) - persists dependency downloads on a shared volume
3. **Docker layer caching** - optimized COPY order in generated Dockerfiles
## Registry-Based Cache (CACHE_IMAGE)
Store BuildKit cache layers in a container registry for sharing across builds, runners, and CI environments.
```bash
docker run --rm \
-v /path/to/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-app:latest \
-e CACHE_IMAGE=registry.io/my-app:buildcache \
miget/migetpacks:latest
```
### Cache Mode
Control how much data is stored in the registry cache with `CACHE_MODE`:
Caches only the final layer. Produces smaller cache images but fewer cache hits on intermediate layers.
```bash
-e CACHE_MODE=min
```
Caches all intermediate layers. Larger cache images but better cache hit rates, especially when only source code changes.
```bash
-e CACHE_MODE=max
```
### Additional Cache Sources (CACHE_FROM)
Read from multiple cache sources (comma-separated). Useful for sharing cache across branches or teams:
```bash
-e CACHE_IMAGE=registry.io/my-app:cache-main \
-e CACHE_FROM=registry.io/my-app:cache-dev,registry.io/my-app:cache-feature
```
`CACHE_FROM` sources are read-only. The build always writes to `CACHE_IMAGE` but can read from both `CACHE_IMAGE` and all `CACHE_FROM` entries.
### Force Fresh Build (NO_CACHE)
Skip reading from cache while still exporting to the cache registry:
```bash
-e NO_CACHE=true
```
When `NO_CACHE=true`:
- `--no-cache` is passed to the build
- `CACHE_FROM` sources are skipped
- Cache is still exported to `CACHE_IMAGE` (so subsequent builds can use it)
### Insecure Registry (CACHE_REGISTRY_INSECURE)
For internal HTTP registries (e.g., a local cache registry), enable insecure mode:
```bash
-e CACHE_IMAGE=http-registry.internal:5000/my-app:buildcache \
-e CACHE_REGISTRY_INSECURE=true
```
## Shared Package Manager Cache (BUILD_CACHE_DIR)
Mount a persistent volume to cache package manager downloads across builds. This is especially effective on self-hosted runners or Kubernetes environments with ReadWriteMany (RWX) PersistentVolumeClaims.
```bash
docker run --rm \
-v /path/to/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /persistent-cache:/cache \
-e OUTPUT_IMAGE=registry.io/my-app:latest \
-e BUILD_CACHE_DIR=/cache \
miget/migetpacks:latest
```
### Cache Directories by Language
| Language | Cache Location | What's Cached |
|----------|---------------|---------------|
| Node.js | `/cache/npm`, `/cache/yarn`, `/cache/pnpm` | Package tarballs |
| Python | `/cache/pip`, `/cache/uv` | Wheel files |
| Ruby | `/cache/bundler` | Gem files |
| Go | `/cache/go` | Module sources |
| Rust | `/cache/cargo` | Crate sources |
| Java | `/cache/maven`, `/cache/gradle` | Dependencies |
| PHP | `/cache/composer` | Composer packages |
BuildKit cache mounts (`--mount=type=cache,sharing=shared`) are automatically added to generated Dockerfiles for all supported languages. The `sharing=shared` mode allows concurrent builds to share cache without locking.
### Kubernetes PVC Example
```yaml
spec:
volumes:
- name: build-cache
persistentVolumeClaim:
claimName: build-cache # RWX PVC
buildSteps:
- name: build
image: miget/migetpacks:latest
volumeMounts:
- name: build-cache
mountPath: /cache
env:
- name: BUILD_CACHE_DIR
value: /cache
```
## Docker Layer Caching
All generated Dockerfiles use optimized COPY ordering to maximize Docker layer cache hits. This requires no configuration and works automatically.
### How It Works
```dockerfile
# 1. Copy dependency files first (changes rarely)
COPY package.json package-lock.json ./
# 2. Install dependencies (CACHED if lockfiles unchanged)
RUN npm ci
# 3. Copy source code (changes frequently)
COPY . .
# 4. Build and cleanup
RUN npm run build && npm prune --production
```
### Cache Behavior
| Scenario | Dependency Install | Build Step |
|----------|-------------------|------------|
| First build | Runs | Runs |
| Source code change only | **CACHED** | Runs |
| Lockfile change | Runs | Runs |
| No changes | **CACHED** | **CACHED** |
This optimization is applied for all languages:
- **Node.js**: `package.json` + lockfile copied first
- **Python**: `requirements.txt` / `pyproject.toml` copied first
- **Ruby**: `Gemfile` + `Gemfile.lock` copied first
- **Go**: `go.mod` + `go.sum` copied first
- **Rust**: `Cargo.toml` + `Cargo.lock` copied first
- **Java**: `pom.xml` / `build.gradle` copied first
- **.NET**: `*.csproj` / `*.sln` copied first
## Combining Strategies
For optimal performance, use all three strategies together:
```bash
docker run --rm \
-v /path/to/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /persistent-cache:/cache \
-e OUTPUT_IMAGE=registry.io/my-app:latest \
-e CACHE_IMAGE=registry.io/my-app:buildcache \
-e CACHE_MODE=max \
-e BUILD_CACHE_DIR=/cache \
-e REGISTRY_MIRROR=https://registry.example.io/mirror \
miget/migetpacks:latest
```
This gives you:
- **Registry cache**: BuildKit layers cached remotely (works across machines)
- **Package cache**: Dependency downloads cached locally (fastest for reinstalls)
- **Layer cache**: Docker layer ordering optimized (fastest for source-only changes)
- **Registry mirror**: Base image pulls from local cache (fastest for image downloads)
## Registry Mirror
Use `REGISTRY_MIRROR` to configure a pull-through cache for Docker Hub base images:
```bash
-e REGISTRY_MIRROR=https://registry.example.io/mirror
```
The mirror is configured in the Docker daemon's `registry-mirrors` setting, so all image pulls automatically try the mirror first before falling back to Docker Hub.
---
url: https://migetpacks.com/guides/custom-dockerfile
---
---
title: "Custom Dockerfile"
description: "Use your own Dockerfile instead of the auto-generated one for projects with specific build requirements."
---
## Overview
migetpacks prioritizes its optimized multi-stage builds for known languages. A `Dockerfile` in your project is only used as a **fallback** when no supported language is detected. To force migetpacks to use your Dockerfile, set `LANGUAGE=dockerfile` or `DOCKERFILE_PATH`.
## Detection Priority
If `DOCKERFILE_PATH` is set, or `LANGUAGE=dockerfile` is specified, your Dockerfile is used immediately.
migetpacks checks for supported languages (Ruby, Python, Go, Node.js, etc.) and generates an optimized build.
Only if no supported language is detected, migetpacks falls back to using `Dockerfile` in your project root.
This priority order ensures you get migetpacks' optimized builds by default. If your project has both `package.json` and `Dockerfile`, migetpacks will detect Node.js and generate its own Dockerfile. Use `LANGUAGE=dockerfile` to override this behavior.
## Using DOCKERFILE_PATH
Point to a specific Dockerfile when it is not in the default location:
```bash
docker run --rm \
-v /path/to/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-app:latest \
-e DOCKERFILE_PATH=docker/Dockerfile.prod \
miget/migetpacks:latest
```
`DOCKERFILE_PATH` is relative to the project root (or `PROJECT_PATH` if set). You can also use an absolute path.
## Examples
### Standard Dockerfile in Root
If your project has a `Dockerfile` in the root directory, no extra configuration is needed:
```text
my-app/
├── Dockerfile # Used automatically
├── src/
├── package.json
└── ...
```
```bash
docker run --rm \
-v /path/to/my-app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-app:latest \
miget/migetpacks:latest
```
### Multiple Dockerfiles
Projects with environment-specific Dockerfiles:
```text
my-app/
├── docker/
│ ├── Dockerfile.dev
│ ├── Dockerfile.prod
│ └── Dockerfile.test
├── src/
└── ...
```
```bash Production
docker run --rm \
-v /path/to/my-app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-app:latest \
-e DOCKERFILE_PATH=docker/Dockerfile.prod \
miget/migetpacks:latest
```
```bash Development
docker run --rm \
-v /path/to/my-app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:dev \
-e DOCKERFILE_PATH=docker/Dockerfile.dev \
miget/migetpacks:latest
```
### Monorepo with Custom Dockerfile
Combine `PROJECT_PATH` and `DOCKERFILE_PATH`:
```bash
docker run --rm \
-v /path/to/monorepo:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-service:latest \
-e PROJECT_PATH=services/api \
-e DOCKERFILE_PATH=Dockerfile.production \
miget/migetpacks:latest
```
This looks for the Dockerfile at `services/api/Dockerfile.production`.
## Use Cases
### Custom Base Images
When you need a specific base image not supported by migetpacks:
```dockerfile
FROM my-internal-registry.io/custom-base:latest
WORKDIR /app
COPY . .
RUN make build
EXPOSE 8080
CMD ["./my-app"]
```
If the base image lives in a private registry, set [`EXTERNAL_REGISTRY_URL` / `EXTERNAL_REGISTRY_USERNAME` / `EXTERNAL_REGISTRY_PASSWORD`](../configuration/environment-variables#external-private-registry) on the build so the builder can authenticate before pulling.
### Complex Multi-Stage Builds
When your build process requires custom stages:
```dockerfile
FROM node:20 AS frontend
WORKDIR /frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build
FROM golang:1.22 AS backend
WORKDIR /backend
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app
FROM gcr.io/distroless/static-debian12
COPY --from=backend /app /app
COPY --from=frontend /frontend/dist /static
EXPOSE 8080
CMD ["/app"]
```
### Specific Optimization Needs
When you need fine-grained control over layer ordering, build arguments, or caching:
```dockerfile
FROM python:3.12-slim AS builder
ARG PIP_INDEX_URL
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq-dev gcc && rm -rf /var/lib/apt/lists/*
# Layer-cached dependency install
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
FROM python:3.12-slim
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /app /app
WORKDIR /app
EXPOSE 5000
CMD ["python", "app.py"]
```
## Build Caching with Custom Dockerfiles
Registry-based caching (`CACHE_IMAGE`) works with custom Dockerfiles:
```bash
docker run --rm \
-v /path/to/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-app:latest \
-e DOCKERFILE_PATH=Dockerfile \
-e CACHE_IMAGE=registry.io/my-app:buildcache \
-e CACHE_MODE=max \
miget/migetpacks:latest
```
`BUILD_CACHE_DIR` and language-specific BuildKit cache mounts are only available in auto-generated Dockerfiles. If you use a custom Dockerfile, you must manage cache mounts yourself.
## What migetpacks Still Provides
Even when using a custom Dockerfile, migetpacks handles:
- Building the image with BuildKit
- Pushing to the target registry (`OUTPUT_IMAGE`)
- Registry-based caching (`CACHE_IMAGE`)
- Architecture targeting (`ARCH`)
- Registry mirror configuration (`REGISTRY_MIRROR`)
- Result file generation (`RESULT_FILE`)
- Port detection from the built image
---
url: https://migetpacks.com/guides/docker-compose
---
---
title: "Docker Compose"
description: "Build multi-service applications from Docker Compose files, with each service pushed as a separate image."
---
## Overview
migetpacks supports Docker Compose files for building multi-service applications. When a compose file is detected, all services with a `build` section are built and pushed to the registry as separate images.
## Detection Priority
migetpacks looks for compose files in the following order:
If set, this path is used directly.
The modern default compose file name.
Alternative YAML extension.
Legacy Docker Compose file name.
Legacy alternative YAML extension.
If none of these files are found, migetpacks falls back to standard language detection and single-image build.
## Image Naming
Each service's image is named by appending the service name to `OUTPUT_IMAGE`:
```text
OUTPUT_IMAGE-{service_name}
```
For example, with `OUTPUT_IMAGE=registry.io/myapp:latest`:
| Service | Image Name |
|---------|-----------|
| api | `registry.io/myapp-api:latest` |
| web | `registry.io/myapp-web:latest` |
| worker | `registry.io/myapp-worker:latest` |
## Example
### compose.yaml
```yaml
services:
api:
build:
context: ./api
dockerfile: Dockerfile
ports:
- "5000:5000"
web:
build:
context: ./web
dockerfile: Dockerfile
ports:
- "3000:3000"
worker:
build: ./worker # shorthand syntax
```
### Project Structure
```text
my-project/
├── compose.yaml
├── api/
│ ├── Dockerfile
│ ├── main.go
│ └── go.mod
├── web/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
└── worker/
├── Dockerfile
├── requirements.txt
└── worker.py
```
### Build Command
```bash
docker run --rm \
-v /path/to/my-project:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/myapp:latest \
miget/migetpacks:latest
```
### Build Output
The build produces three images:
- `registry.io/myapp-api:latest`
- `registry.io/myapp-web:latest`
- `registry.io/myapp-worker:latest`
## Custom Compose File
Use `COMPOSE_FILE` to specify a non-standard compose file:
```bash Production Compose
docker run --rm \
-v /path/to/project:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/myapp:latest \
-e COMPOSE_FILE=docker-compose.prod.yaml \
miget/migetpacks:latest
```
```bash Staging Compose
docker run --rm \
-v /path/to/project:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/myapp:staging \
-e COMPOSE_FILE=docker-compose.staging.yaml \
miget/migetpacks:latest
```
## Compose File Syntax
migetpacks supports both the full and shorthand `build` syntax:
```yaml
services:
api:
build:
context: ./api
dockerfile: Dockerfile
args:
NODE_ENV: production
```
```yaml
services:
api:
build: ./api
```
This is equivalent to setting `context: ./api` with the default `Dockerfile`.
## Services Without Build Sections
Services without a `build` section (e.g., database services using pre-built images) are skipped:
```yaml
services:
api:
build: ./api # Built and pushed
web:
build: ./web # Built and pushed
postgres:
image: postgres:16 # Skipped (no build section)
redis:
image: redis:7 # Skipped (no build section)
```
Only `api` and `web` are built; `postgres` and `redis` are ignored.
## Build Caching with Compose
Registry-based caching works with compose builds. Each service gets its own cache image:
```bash
docker run --rm \
-v /path/to/project:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/myapp:latest \
-e CACHE_IMAGE=registry.io/myapp:buildcache \
miget/migetpacks:latest
```
## CI/CD Example
### GitHub Actions
```yaml
name: Build Multi-Service App
on:
push:
branches: [main]
jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Log into registry
uses: docker/login-action@v3
with:
registry: your-registry.io
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build all services
run: |
docker run --rm \
-v ${{ github.workspace }}:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=your-registry.io/myapp:${{ github.sha }} \
miget/migetpacks:latest
```
This produces:
- `your-registry.io/myapp-api:{sha}`
- `your-registry.io/myapp-web:{sha}`
- `your-registry.io/myapp-worker:{sha}`
---
url: https://migetpacks.com/guides/github-actions
---
---
title: "GitHub Actions"
description: "Use migetpacks in GitHub Actions workflows for automated container builds on self-hosted and GitHub-hosted runners."
---
## Overview
migetpacks integrates with GitHub Actions to build and push container images as part of your CI/CD pipeline. It works with both self-hosted runners (recommended for performance) and GitHub-hosted runners.
## Self-Hosted Runner
Self-hosted runners offer the best performance because Docker layer caches and package manager caches persist between builds.
```yaml
name: Build with migetpacks
on:
push:
branches: [main]
jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Log into registry
uses: docker/login-action@v3
with:
registry: your-registry.io
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Setup cache directory
run: mkdir -p /home/runner/migetpacks-cache
- name: Build with migetpacks
run: |
docker run --rm \
-v ${{ github.workspace }}:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /home/runner/migetpacks-cache:/cache \
-e OUTPUT_IMAGE=your-registry.io/app:${{ github.sha }} \
-e BUILD_CACHE_DIR=/cache \
-e REGISTRY_MIRROR=https://registry.example.io/mirror \
-e USE_DHI=true \
miget/migetpacks:latest
```
The persistent cache directory (`/home/runner/migetpacks-cache`) survives runner restarts and dramatically speeds up dependency installation on subsequent builds.
## GitHub-Hosted Runner
GitHub-hosted runners do not persist Docker layer caches between runs. Use `CACHE_IMAGE` for registry-based caching to speed up builds.
```yaml
name: Build with migetpacks
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log into registry
uses: docker/login-action@v3
with:
registry: your-registry.io
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build with migetpacks
run: |
docker run --rm \
-v ${{ github.workspace }}:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=your-registry.io/app:${{ github.sha }} \
-e CACHE_IMAGE=your-registry.io/app:buildcache \
-e CACHE_MODE=max \
miget/migetpacks:latest
```
## Configuration Options
### Registry Login
Always authenticate before running migetpacks if you are pushing to a private registry:
```yaml Docker Hub
- name: Log into Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
```
```yaml GitHub Container Registry
- name: Log into GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
```
```yaml AWS ECR
- name: Log into ECR
uses: docker/login-action@v3
with:
registry: 123456789.dkr.ecr.us-east-1.amazonaws.com
username: ${{ secrets.AWS_ACCESS_KEY_ID }}
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
```
### Build Cache Directory
`BUILD_CACHE_DIR` enables shared package manager caching. On self-hosted runners, point this to a persistent directory:
```yaml
- name: Setup cache directory
run: mkdir -p /home/runner/migetpacks-cache
- name: Build with migetpacks
run: |
docker run --rm \
-v /home/runner/migetpacks-cache:/cache \
-e BUILD_CACHE_DIR=/cache \
...
```
### Registry Mirror
`REGISTRY_MIRROR` configures a pull-through cache for Docker Hub images, reducing image pull times:
```yaml
-e REGISTRY_MIRROR=https://your-mirror.example.com
```
### Custom Environment Variables
Pass build-time environment variables directly:
```yaml
- name: Build with migetpacks
run: |
docker run --rm \
-v ${{ github.workspace }}:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=your-registry.io/app:${{ github.sha }} \
-e NODE_OPTIONS="--max-old-space-size=4096" \
-e NEXT_PUBLIC_API_URL="${{ vars.API_URL }}" \
-e RAILS_MASTER_KEY="${{ secrets.RAILS_MASTER_KEY }}" \
miget/migetpacks:latest
```
Secrets like `RAILS_MASTER_KEY` are injected as build-time environment variables and will be present in the generated Dockerfile. They are visible in the final image's environment. Use runtime secrets injection for sensitive values in production.
## Full Workflow Example
This complete workflow builds on push to main, tags with both commit SHA and `latest`, and uses all available caching strategies:
```yaml
name: Build and Push
on:
push:
branches: [main]
jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Log into registry
uses: docker/login-action@v3
with:
registry: your-registry.io
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Setup persistent cache
run: mkdir -p /home/runner/migetpacks-cache
- name: Build with migetpacks
run: |
docker run --rm \
-v ${{ github.workspace }}:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /home/runner/migetpacks-cache:/cache \
-e OUTPUT_IMAGE=your-registry.io/app:${{ github.sha }} \
-e BUILD_CACHE_DIR=/cache \
-e CACHE_IMAGE=your-registry.io/app:buildcache \
-e CACHE_MODE=max \
-e REGISTRY_MIRROR=https://registry.example.io/mirror \
-e USE_DHI=true \
miget/migetpacks:latest
```
## Performance Tips
- **Persistent cache**: Use `BUILD_CACHE_DIR` pointed at a local directory
- **Pre-pull images**: Pull common base images during runner initialization
- **Registry mirror**: Use `REGISTRY_MIRROR` for Docker Hub images
- **Local Docker cache**: Docker layer cache persists in `/var/lib/docker`
- **NVMe storage**: Use local SSD/NVMe for `/var/lib/docker` instead of network storage
- **Registry cache**: Use `CACHE_IMAGE` with `CACHE_MODE=max` for best cache hits
- **Additional sources**: Use `CACHE_FROM` to read from multiple cache images
- **Smaller images**: Use `USE_DHI=true` for distroless runtime images
---
url: https://migetpacks.com/guides/hardened-images
---
---
title: "Docker Hardened Images"
description: "Build secure, minimal containers using Docker Hardened Images (DHI) with distroless runtime environments."
---
## Overview
Docker Hardened Images (DHI) from `dhi.io` provide secure, CVE-free base images with minimal attack surfaces. When enabled, migetpacks uses DHI images for both the build stage (`-dev` variants with shell) and the runtime stage (distroless, no shell).
## Enabling DHI
Set `USE_DHI=true` to enable Docker Hardened Images:
```bash
docker run --rm \
-v /path/to/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-app:latest \
-e USE_DHI=true \
miget/migetpacks:latest
```
## Registry Authentication
The `dhi.io` registry requires authentication. How you provide credentials depends on your environment.
### CI/CD Environments (GitHub Actions, GitLab CI)
In CI/CD pipelines, credentials are typically available via secrets. The easiest approach is to pass them as environment variables:
```bash
docker run --rm \
-v /path/to/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:latest \
-e USE_DHI=true \
-e DHI_USERNAME=$DHI_USERNAME \
-e DHI_PASSWORD=$DHI_PASSWORD \
miget/migetpacks:latest
```
Or create a Docker config file with credentials and mount it:
```yaml
# GitHub Actions example
- name: Create DHI auth config
run: |
mkdir -p /tmp/docker-config
echo '{"auths":{"dhi.io":{"auth":"'$(echo -n "${{ secrets.DHI_USERNAME }}:${{ secrets.DHI_PASSWORD }}" | base64)'"}}}' > /tmp/docker-config/config.json
- name: Build with DHI
run: |
docker run --rm \
-v $(pwd):/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /tmp/docker-config/config.json:/root/.docker/config.json:ro \
-e OUTPUT_IMAGE=my-app:latest \
-e USE_DHI=true \
miget/migetpacks:latest
```
### Local Development (Docker Desktop)
Docker Desktop on Mac and Windows stores credentials in the system keychain (`credsStore: desktop`), not in `~/.docker/config.json`. When you run `docker login dhi.io`, the credentials are stored in the keychain, but the migetpacks container cannot access them.
Extract the credentials from the keychain and pass them as environment variables:
```bash
# Extract credentials from Docker Desktop keychain
DHI_USERNAME=$(echo "dhi.io" | docker-credential-desktop get | jq -r '.Username')
DHI_PASSWORD=$(echo "dhi.io" | docker-credential-desktop get | jq -r '.Secret')
# Build with DHI
docker run --rm \
-v "$(pwd):/workspace/source:ro" \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
-e ARCH=arm64 \
-e USE_DHI=true \
-e DHI_USERNAME="$DHI_USERNAME" \
-e DHI_PASSWORD="$DHI_PASSWORD" \
miget/migetpacks:latest
```
Add this to your shell profile (`.bashrc` or `.zshrc`) for convenience:
```bash
alias migetpacks-dhi='DHI_USERNAME=$(echo "dhi.io" | docker-credential-desktop get | jq -r ".Username") DHI_PASSWORD=$(echo "dhi.io" | docker-credential-desktop get | jq -r ".Secret") docker run --rm -v "$(pwd):/workspace/source:ro" -v /var/run/docker.sock:/var/run/docker.sock -e USE_DHI=true -e DHI_USERNAME="$DHI_USERNAME" -e DHI_PASSWORD="$DHI_PASSWORD"'
```
Then use: `migetpacks-dhi -e OUTPUT_IMAGE=my-app:local miget/migetpacks:latest`
### Linux
On Linux, credentials are typically stored directly in `~/.docker/config.json` after running `docker login dhi.io`. Mount this file:
```bash
docker run --rm \
-v "$(pwd):/workspace/source:ro" \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ~/.docker/config.json:/root/.docker/config.json:ro \
-e OUTPUT_IMAGE=my-app:local \
-e USE_DHI=true \
miget/migetpacks:latest
```
If you're using a credential helper (like `pass` or `secretservice`), extract credentials and pass them as environment variables:
```bash
# For pass credential helper
DHI_USERNAME=$(echo "dhi.io" | docker-credential-pass get | jq -r '.Username')
DHI_PASSWORD=$(echo "dhi.io" | docker-credential-pass get | jq -r '.Secret')
# For secretservice credential helper
DHI_USERNAME=$(echo "dhi.io" | docker-credential-secretservice get | jq -r '.Username')
DHI_PASSWORD=$(echo "dhi.io" | docker-credential-secretservice get | jq -r '.Secret')
```
### Kubernetes/Shipwright
When running migetpacks in Kubernetes via Shipwright, use an `imagePullSecret` containing your DHI credentials:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: dhi-credentials
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson:
```
Then reference it in your ClusterBuildStrategy or Build resource.
## Image Variants
The `-dev` variants include:
- Shell (`/bin/sh`)
- Package manager (`apt-get`)
- Build tools (compilers, headers)
Used during the build stage for installing dependencies and compiling code.
The runtime variants are distroless:
- No shell
- No package manager
- No unnecessary binaries
- Minimal attack surface
- Non-root user: `nonroot`
Used for the final container that runs in production.
## Supported Languages
| Language | Build Image | Runtime Image | Notes |
|----------|-------------|---------------|-------|
| Node.js | `dhi.io/node:{version}-dev` | `dhi.io/node:{version}` | Distroless runtime |
| Deno | `dhi.io/deno:{version}-dev` | `dhi.io/deno:{version}` | Requires 2.6.4+ |
| Bun | `dhi.io/bun:{version}-dev` | `dhi.io/bun:{version}` | Requires 1.3.6+ |
| Ruby | `dhi.io/ruby:{version}-dev` | `dhi.io/ruby:{version}` | Libraries copied from builder |
| Python | `dhi.io/python:{version}-dev` | `dhi.io/python:{version}` | Distroless runtime |
| Go | `dhi.io/golang:{version}-dev` | `dhi.io/golang:{version}` | Requires 1.22+ |
| Rust | `dhi.io/rust:{version}-dev` | `dhi.io/rust:{version}` | Distroless runtime |
| Java | `dhi.io/eclipse-temurin:{version}-jdk-dev` | `dhi.io/eclipse-temurin:{version}` | Maven/Gradle installed in builder |
| Kotlin | `dhi.io/eclipse-temurin:{version}-jdk-dev` | `dhi.io/eclipse-temurin:{version}` | Reads system.properties |
| Scala | `sbtscala/scala-sbt:...` (official) | `dhi.io/eclipse-temurin:{java}` | Official build, DHI runtime |
| Clojure | `clojure:temurin-{java}-lein` (official) | `dhi.io/eclipse-temurin:{java}` | Official build, DHI runtime |
| .NET | `dhi.io/dotnet:{version}-sdk` | `dhi.io/aspnetcore:{version}` | Distroless runtime |
**Not supported with DHI:**
- **PHP** - FrankenPHP is a specialized server that requires official FrankenPHP images
- **Elixir** - Uses official Elixir images (no DHI equivalent available)
## DHI Mirror
If you have a private mirror for DHI images (e.g., behind a firewall), use `DHI_MIRROR`:
```bash
docker run --rm \
-v /path/to/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-app:latest \
-e USE_DHI=true \
-e DHI_MIRROR=https://registry.example.io/dhi-io \
miget/migetpacks:latest
```
## Deployment Considerations
Distroless containers have important behavioral differences from traditional containers.
### No Shell Available
Commands cannot use shell features like pipes, variable expansion, or shebangs:
```bash Standard (with shell)
CMD ["sh", "-c", "node server.js --port ${PORT:-5000}"]
```
```bash DHI (exec format)
CMD ["node", "server.js", "--port", "5000"]
```
Shell variables like `${PORT:-5000}` must be pre-expanded to literal values. migetpacks handles this automatically during Dockerfile generation.
### Ruby-Specific Considerations
Ruby scripts with shebangs (`#!/usr/bin/env ruby`) do not work in distroless containers:
- Process commands are transformed: `./bin/rails` becomes `ruby bin/rails`
- `BUNDLER_VERSION` is set from `Gemfile.lock` to prevent bundler auto-switching (which requires `/usr/bin/env`)
- Native gem shared libraries (`.so` files) are copied from the builder stage
- PATH includes `/app/bin:/app/vendor/bundle/bin`
### Java-Specific Considerations
- Eclipse Temurin JDK images do not include Maven or Gradle
- Build tools are installed via `apt-get` in the builder stage
- Runtime uses distroless JRE (no JDK)
- Version normalization applies: `1.8` becomes `8`, `1.11` becomes `11`
### Go Version Requirements
Go DHI images require version 1.22 or newer, with minimum patch versions:
- Go 1.22.12+
- Go 1.23.7+
- Go 1.24.1+
### Result JSON
When using DHI, the build result JSON includes `_shell: false` to signal that the container is distroless:
```json
{
"status": "success",
"images": [
{"name": "registry.io/my-app", "ports": ["5000/tcp"]}
],
"_shell": false
}
```
This can be used by deployment systems to adjust health check and exec strategies.
## Multi-Buildpack with DHI
When using `BUILDPACKS` with `USE_DHI=true`, only the primary language runtime is available in the final distroless container. Secondary buildpacks work during the build stage but their runtimes are not included in the runtime image.
This is acceptable when secondary buildpacks produce build artifacts (e.g., Node.js compiling frontend assets) rather than running at runtime.
---
url: https://migetpacks.com/guides/local-docker
---
---
title: "Local Docker Builds"
description: "Build and test container images locally using Docker Desktop without pushing to a remote registry."
---
## Overview
migetpacks can build container images locally using your Docker socket. When `OUTPUT_IMAGE` does not contain a registry prefix, the built image stays on your local Docker daemon and is never pushed to a remote registry.
## Prerequisites
- Docker Desktop (Mac/Windows) or Docker Engine (Linux)
- Docker daemon running
- The migetpacks image pulled locally
```bash
docker pull miget/migetpacks:latest
```
migetpacks uses Docker-in-Docker internally. Docker Desktop on Mac and Windows handles this transparently through its Linux VM. No `--privileged` flag or special configuration is needed.
## Building Locally
Mount your application source code and the Docker socket, then run migetpacks:
```bash
docker run --rm \
-v $(pwd):/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
miget/migetpacks:latest
```
```powershell
docker run --rm `
-v ${PWD}:/workspace/source:ro `
-v /var/run/docker.sock:/var/run/docker.sock `
-e OUTPUT_IMAGE=my-app:local `
miget/migetpacks:latest
```
```bash
docker run --rm \
-v $(pwd):/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
miget/migetpacks:latest
```
Then test the built image:
```bash
docker run --rm -p 5000:5000 my-app:local
```
When `OUTPUT_IMAGE` does not contain a `/` with a registry domain (e.g., `my-app:local` vs `registry.io/my-app:latest`), the builder uses the local Docker daemon and the image will not be pushed.
## Language Override
By default, migetpacks auto-detects the language. You can override this with the `LANGUAGE` environment variable:
```bash Node.js
docker run --rm \
-v /path/to/your/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
-e LANGUAGE=nodejs \
miget/migetpacks:latest
```
```bash Python
docker run --rm \
-v /path/to/your/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
-e LANGUAGE=python \
miget/migetpacks:latest
```
```bash Ruby
docker run --rm \
-v /path/to/your/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
-e LANGUAGE=ruby \
miget/migetpacks:latest
```
## Custom Environment Variables
Any environment variable not recognized by migetpacks is automatically injected into the generated Dockerfile as an `ENV` statement:
```bash
docker run --rm \
-v /path/to/your/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
-e NODE_OPTIONS="--max-old-space-size=4096" \
-e NEXT_PUBLIC_API_URL="http://localhost:3000" \
miget/migetpacks:latest
```
## Debugging Tips
### Inspect Built Image Contents
```bash
docker run --rm my-app:local ls -la /app/
```
### Check Environment Variables
```bash
docker run --rm my-app:local env
```
### View the Generated Dockerfile
The build output includes the generated Dockerfile content. Look for the `Dockerfile.runtime` section in the build logs.
### Interactive Shell
If the build succeeds but the app does not start correctly, you can override the entrypoint:
```bash
docker run --rm -it --entrypoint /bin/sh my-app:local
```
DHI (Docker Hardened Images) containers are distroless and do not have a shell. You cannot use `--entrypoint /bin/sh` with DHI-built images. Use standard (non-DHI) builds for debugging.
### Check Exposed Ports
```bash
docker inspect my-app:local --format='{{json .Config.ExposedPorts}}'
```
## Architecture Override
By default, migetpacks builds for `x86_64`. To build for a different architecture:
```bash
docker run --rm \
-v /path/to/your/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
-e ARCH=arm64 \
miget/migetpacks:latest
```
## Source Mount Options
Mount the source as read-only to prevent any modifications:
```bash
-v /path/to/your/app:/workspace/source:ro
```
If your build process needs to write to the source directory:
```bash
-v /path/to/your/app:/workspace/source
```
For monorepo projects, mount the entire repo and use `PROJECT_PATH`:
```bash
-v /path/to/monorepo:/workspace/source:ro \
-e PROJECT_PATH=services/api
```
---
url: https://migetpacks.com/guides/monorepo
---
---
title: "Monorepo Support"
description: "Build specific services or packages within a monorepo using PROJECT_PATH for subdirectory targeting."
---
## Overview
migetpacks supports monorepo projects through the `PROJECT_PATH` environment variable. This allows you to target a specific subdirectory within your source tree for language detection, version detection, and building.
## Basic Usage
Set `PROJECT_PATH` to the subdirectory you want to build:
```bash
docker run --rm \
-v /path/to/monorepo:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-api:latest \
-e PROJECT_PATH=services/api \
miget/migetpacks:latest
```
`PROJECT_PATH` is relative to `SOURCE_DIR` (defaults to `/workspace/source`). The builder will detect language and version from the specified subdirectory.
## Example Monorepo Structure
```text
my-monorepo/
├── services/
│ ├── api/ # Go service
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── main.go
│ ├── web/ # Node.js frontend
│ │ ├── package.json
│ │ └── src/
│ └── worker/ # Python worker
│ ├── requirements.txt
│ └── worker.py
├── shared/
│ └── proto/
└── docker-compose.yaml
```
### Building Each Service
```bash API Service (Go)
docker run --rm \
-v /path/to/monorepo:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-api:latest \
-e PROJECT_PATH=services/api \
miget/migetpacks:latest
```
```bash Web Frontend (Node.js)
docker run --rm \
-v /path/to/monorepo:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-web:latest \
-e PROJECT_PATH=services/web \
miget/migetpacks:latest
```
```bash Worker (Python)
docker run --rm \
-v /path/to/monorepo:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-worker:latest \
-e PROJECT_PATH=services/worker \
miget/migetpacks:latest
```
## Combining with DOCKERFILE_PATH
For services that have custom Dockerfiles in non-standard locations:
```bash
docker run --rm \
-v /path/to/monorepo:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-api:latest \
-e PROJECT_PATH=services/api \
-e DOCKERFILE_PATH=docker/Dockerfile.prod \
miget/migetpacks:latest
```
When both `PROJECT_PATH` and `DOCKERFILE_PATH` are set, `DOCKERFILE_PATH` is resolved relative to the `PROJECT_PATH` subdirectory. In the example above, the Dockerfile is located at `services/api/docker/Dockerfile.prod`.
## CI/CD with Monorepos
### GitHub Actions Matrix
Build multiple services in parallel using a matrix strategy:
```yaml
name: Build Services
on:
push:
branches: [main]
jobs:
build:
runs-on: self-hosted
strategy:
matrix:
service:
- name: api
path: services/api
language: go
- name: web
path: services/web
language: nodejs
- name: worker
path: services/worker
language: python
steps:
- uses: actions/checkout@v4
- name: Log into registry
uses: docker/login-action@v3
with:
registry: your-registry.io
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build ${{ matrix.service.name }}
run: |
docker run --rm \
-v ${{ github.workspace }}:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=your-registry.io/${{ matrix.service.name }}:${{ github.sha }} \
-e PROJECT_PATH=${{ matrix.service.path }} \
miget/migetpacks:latest
```
### Shipwright BuildRun
```yaml
apiVersion: shipwright.io/v1beta1
kind: BuildRun
metadata:
name: api-build
spec:
build:
name: my-monorepo
paramValues:
- name: PROJECT_PATH
value: services/api
- name: OUTPUT_IMAGE
value: registry.io/my-api:latest
```
## How Language Detection Works with PROJECT_PATH
When `PROJECT_PATH` is set, language detection examines only the files within that subdirectory:
The effective source becomes `SOURCE_DIR/PROJECT_PATH` (e.g., `/workspace/source/services/api`).
Files like `go.mod`, `package.json`, `requirements.txt`, etc. are searched within the subdirectory.
Version files (`.nvmrc`, `.ruby-version`, `go.mod`, etc.) are read from the subdirectory.
The Docker build context is the subdirectory, so only those files are included in the image.
## Tips
- Each service gets its own `OUTPUT_IMAGE`, so they can be deployed independently
- Language detection is automatic per subdirectory, no need to set `LANGUAGE` manually
- Shared dependencies (e.g., Go workspaces) should be handled via the service's own dependency management
- Use `BUILD_CACHE_DIR` with a shared volume to speed up builds across services
---
url: https://migetpacks.com/guides/multi-buildpack
---
---
title: "Multi-Buildpack"
description: "Combine multiple language runtimes in a single build for applications that need more than one language."
---
## Overview
migetpacks supports multi-buildpack builds for applications that require multiple language runtimes. For example, a Ruby on Rails app that uses Node.js for asset compilation, or a Python app with a Node.js frontend build step.
## How It Works
Multi-buildpack builds use Docker multi-stage builds to copy runtimes from official images into the primary builder stage. All runtimes are available during the build, and builds run sequentially.
```dockerfile
# Primary builder with additional runtimes
FROM ruby:3.2 AS builder
WORKDIR /build
# Copy Python runtime from python image
COPY --from=python:3.11 /usr/local/bin/python* /usr/local/bin/
COPY --from=python:3.11 /usr/local/lib/python3.11 /usr/local/lib/python3.11
# Copy Node.js runtime from node image
COPY --from=node:20 /usr/local/bin/node /usr/local/bin/
COPY --from=node:20 /usr/local/lib/node_modules /usr/local/lib/node_modules
# All runtimes available - run builds sequentially
RUN bundle install
RUN pip install -r requirements.txt
RUN npm install && npm run build
```
## Automatic Detection
migetpacks automatically adds Node.js as a secondary buildpack when:
- The primary language is **not** Node.js
- A `package.json` file exists in the project root
This handles the common case of Ruby/Python/Go apps that use Node.js for frontend asset compilation.
Automatic Node.js detection only adds Node.js as a secondary buildpack for asset compilation. It does not change the primary language or the runtime base image.
## Explicit Buildpack Ordering
Use the `BUILDPACKS` environment variable to explicitly control which buildpacks run and in what order:
```bash
docker run --rm \
-v /path/to/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:latest \
-e BUILDPACKS=ruby,python,nodejs \
miget/migetpacks:latest
```
The first entry determines the base image, runtime container, and main process. In the example above, `ruby` is the primary language.
Additional entries (`python`, `nodejs`) copy their runtimes into the builder stage and execute their respective build commands.
Each buildpack's install and build commands run in order within the same builder stage.
### Format
The `BUILDPACKS` value accepts both short and namespaced formats:
```bash Short Format
BUILDPACKS=ruby,python,nodejs
```
```bash Namespaced Format
BUILDPACKS=miget/ruby,miget/python,miget/nodejs
```
When `BUILDPACKS` is set, automatic detection is disabled. Only the specified buildpacks will be used.
## Common Use Cases
### Ruby + Node.js (Asset Compilation)
Rails apps with Webpack, esbuild, or Vite for JavaScript bundling:
```bash
BUILDPACKS=ruby,nodejs
```
This installs Node.js dependencies (`npm install` or `yarn install`) and runs the build command (`npm run build`) before the Ruby app is finalized.
### Ruby + Python (AI/ML)
Ruby apps that use Python libraries for machine learning or data processing:
```bash
BUILDPACKS=ruby,python
```
### Python + Node.js (Frontend Build)
Python API with a Node.js-built frontend:
```bash
BUILDPACKS=python,nodejs
```
### Full Stack Example
Ruby app with Python for AI features and Node.js for assets:
```bash
docker run --rm \
-v /path/to/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-app:latest \
-e BUILDPACKS=ruby,python,nodejs \
miget/migetpacks:latest
```
## Runtime Container
In the final runtime container (non-DHI builds), all secondary runtimes are copied from the builder stage:
```dockerfile
# Runtime stage: copy runtimes from builder
FROM ruby:3.2-slim
COPY --from=builder /usr/local/bin/python* /usr/local/bin/
COPY --from=builder /usr/local/bin/node /usr/local/bin/
COPY --from=builder /build /app
```
This means all languages are available at runtime, not just during the build.
## DHI Limitation
When using `USE_DHI=true` with multiple buildpacks, only the primary language runtime is available in the final container. DHI images are distroless and single-runtime.
- **Build-time**: All buildpacks work correctly (uses `-dev` images with shell and package manager)
- **Runtime**: Only the primary language is available (distroless images cannot include additional runtimes)
A warning is displayed when using `BUILDPACKS` with `USE_DHI=true`.
For DHI builds, ensure your application only needs secondary buildpacks at build time (e.g., Node.js for asset compilation that produces static files consumed by the primary runtime).
## app.json Buildpacks
You can also specify buildpacks via `app.json`:
```json
{
"buildpacks": [
{"url": "heroku/nodejs"},
{"url": "heroku/ruby"}
]
}
```
migetpacks maps Heroku buildpack names to its internal language identifiers (e.g., `heroku/ruby` becomes `ruby`).
---
url: https://migetpacks.com/guides/procfile
---
---
title: "Procfile"
description: "Define process types with Procfile for web servers, workers, and other processes."
---
## Overview
migetpacks supports Procfiles for defining process types in your application. A Procfile declares the commands used to run different process types (web server, background workers, scheduled tasks, etc.).
## Procfile Format
A Procfile is a plain text file named `Procfile` (no extension) in the project root. Each line declares a process type:
```text
:
```
### Example Procfile
```text
web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq
release: bundle exec rails db:migrate
clock: bundle exec clockwork clock.rb
```
## Process Priority
migetpacks determines the main process command using the following priority order:
If set, this always takes precedence over any Procfile entry.
```bash
-e RUN_COMMAND="node server.js"
```
The `web` process type is the standard entry point for HTTP-serving applications.
If no `web:` entry exists, the first process declared in the Procfile is used.
If no Procfile exists and `RUN_COMMAND` is not set, a language-specific default is used (e.g., `node server.js`, `python app.py`).
## Language Examples
```text
web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq
release: bundle exec rails db:migrate
```
```text
web: node server.js
worker: node worker.js
```
```text
web: gunicorn app:app --bind 0.0.0.0:5000
worker: celery -A tasks worker --loglevel=info
```
```text
web: ./bin/app serve
worker: ./bin/app worker
```
```text
web: java -jar target/app.jar
worker: java -jar target/worker.jar
```
## Result JSON
Process information from the Procfile is recorded in the build result JSON (when `RESULT_FILE` is set):
```json
{
"status": "success",
"processes": {
"web": "bundle exec puma -C config/puma.rb",
"release": "bundle exec rails db:migrate",
"workers": [
{"name": "sidekiq", "command": "bundle exec sidekiq"},
{"name": "clock", "command": "bundle exec clockwork clock.rb"}
]
}
}
```
### Process Classification
| Process Type | Classification | Description |
|-------------|---------------|-------------|
| `web` | Main process | HTTP-serving entry point |
| `release` | Release script | Run during deployment (e.g., migrations) |
| Any other | Worker | Background processes |
## Overriding with RUN_COMMAND
You can override the Procfile's web process without modifying the file:
```bash
docker run --rm \
-v /path/to/app:/workspace/source:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-app:latest \
-e RUN_COMMAND="bundle exec puma -p 5000 -w 4" \
miget/migetpacks:latest
```
`RUN_COMMAND` only overrides the main container command (equivalent to Docker `CMD`). Worker processes from the Procfile are still recorded in the result JSON for deployment systems that support multiple process types.
## DHI Considerations
When building with `USE_DHI=true`, Procfile commands are adjusted for distroless containers:
- Shell-style commands are converted to exec format
- Ruby shebang scripts (e.g., `./bin/rails`) are prefixed with `ruby` (e.g., `ruby bin/rails`)
- Shell variable expansions like `${PORT:-5000}` are pre-expanded to literal values
## Port Configuration
The `PORT` environment variable (default: `5000`) is exposed in the container. Your Procfile command should bind to this port:
```bash Using PORT variable
web: bundle exec puma -p $PORT
```
```bash Hardcoded port
web: bundle exec puma -p 5000
```
```bash Custom port
# Set PORT=8080 when running migetpacks
web: node server.js --port $PORT
```
## app.json Integration
If your project includes an `app.json` file, process formation (replicas) can be configured alongside the Procfile:
```json
{
"formation": {
"web": {"quantity": 2, "size": "standard-1x"},
"worker": {"quantity": 1}
}
}
```
This formation data is included in the result JSON for deployment systems:
```json
{
"formation": [
{"name": "web", "quantity": 2, "size": "standard-1x"},
{"name": "worker", "quantity": 1, "size": null}
]
}
```
---
url: https://migetpacks.com/how-it-works
---
---
title: "How It Works"
description: "Detection → Version → Dockerfile → Build"
---
migetpacks builds your application in four phases. Each phase is handled by a dedicated script inside the builder container.
```mermaid
graph LR
A[Source Code] --> B[Detect Language]
B --> C[Detect Version]
C --> D[Generate Dockerfile]
D --> E[Build & Push]
```
## Phase 1: Language Detection
The `bin/detect` script scans your source directory for language-specific marker files. It checks each language in priority order and returns the first match.
| File Present | Detected Language |
|-------------|-------------------|
| `Dockerfile` | dockerfile |
| `compose.yaml` / `docker-compose.yml` | compose |
| `Gemfile` | ruby |
| `package.json` | nodejs |
| `deno.json` / `deno.jsonc` | deno |
| `bun.lockb` / `bunfig.toml` | bun |
| `requirements.txt` / `pyproject.toml` / `setup.py` | python |
| `go.mod` | go |
| `Cargo.toml` | rust |
| `pom.xml` / `build.gradle` | java |
| `build.gradle.kts` | kotlin |
| `build.sbt` | scala |
| `project.clj` | clojure |
| `*.csproj` / `*.sln` / `*.slnx` | dotnet |
| `mix.exs` | elixir |
| `composer.json` | php |
Skip auto-detection by setting the `LANGUAGE` environment variable:
```bash
-e LANGUAGE=ruby
```
This is useful for polyglot repos where multiple marker files exist.
If a `Dockerfile` is present, migetpacks uses it directly instead of generating one. Set `LANGUAGE` explicitly to override this behavior.
## Phase 2: Version Detection
The `bin/detect-version` script reads language-specific version files and normalizes them for Docker image tags.
| Language | Version Sources (priority order) |
|----------|----------------------------------|
| Node.js | `.node-version` → `.nvmrc` → `package.json` engines.node |
| Ruby | `.ruby-version` → `Gemfile` ruby declaration |
| Python | `.python-version` → `runtime.txt` → `uv.lock` → `pyproject.toml` |
| Go | `.go-version` → `go.mod` go directive |
| Rust | `rust-toolchain` → `rust-toolchain.toml` channel |
| Java | `.java-version` → `system.properties` → `pom.xml` |
| .NET | `global.json` → `*.csproj` TargetFramework |
| Elixir | `.elixir-version` → `.tool-versions` (asdf) |
| Deno | `.deno-version` → `.dvmrc` → `deno.json` version |
| Bun | `.bun-version` → `bunfig.toml` version |
| PHP | `.php-version` → `composer.json` require.php |
Versions are normalized for Docker tag compatibility:
```text
20.x → 20
>=18.0.0 → 18.0
^3.2.0 → 3.2
20.x || 22.x → 22
3.12.8 → 3.12.8 (kept as-is)
```
The goal is to match available Docker Hub image tags while respecting the developer's version constraint.
## Phase 3: Dockerfile Generation
The `bin/build` script generates an optimized multi-stage Dockerfile using the detected language and version. The generated Dockerfile follows best practices for layer caching.
### Example: Node.js Application
For a Node.js app with version `20` detected from `.nvmrc`, migetpacks generates:
```dockerfile
# Stage 1: Builder
FROM node:20 AS builder
WORKDIR /build
# Dependencies first (cached when lockfile unchanged)
COPY package.json package-lock.json ./
RUN npm ci
# Source code (changes frequently)
COPY . .
RUN npm run build && npm prune --production
# Cleanup dev/test files
RUN rm -rf .git .github test tests spec __tests__ coverage .nyc_output .cache
# Stage 2: Runtime (minimal image)
FROM node:20-slim
RUN groupadd -g 1000 miget && useradd -u 1000 -g miget -m miget
WORKDIR /app
COPY --from=builder --chown=miget:miget /build .
USER miget
EXPOSE 5000
CMD ["node", "server.js"]
```
### Layer Caching Strategy
The key optimization is separating dependency installation from source code copying:
```text
Step 1: COPY lockfiles → Changes rarely → CACHED
Step 2: RUN install deps → Depends on Step 1 → CACHED (if lockfiles unchanged)
Step 3: COPY source code → Changes frequently → Rebuilt
Step 4: RUN build + prune → Depends on Step 3 → Rebuilt
```
This means that when only your source code changes (the common case), dependency installation is skipped entirely.
### Custom Environment Variables
Any environment variable not in the known builder vars list is automatically injected into the generated Dockerfile:
```bash
# Pass build-time env vars
docker run --rm \
-v ./app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
-e NODE_OPTIONS="--max-old-space-size=4096" \
-e NEXT_PUBLIC_API_URL="https://api.example.com" \
miget/migetpacks:latest
```
These become `ENV` statements in the generated Dockerfile, available at both build time and runtime.
## Phase 4: Build and Push
The final phase uses Docker-in-Docker with BuildKit to build and push the image.
A Docker-in-Docker daemon starts inside the builder container with BuildKit enabled.
Registry credentials and mirrors are configured in the daemon. If `REGISTRY_MIRROR` is set, it is added as a pull-through cache.
The generated Dockerfile is built with `docker buildx build`. Cache is read from and written to `CACHE_IMAGE` if configured.
If `OUTPUT_IMAGE` contains a registry prefix, the image is pushed. Otherwise, it is loaded into the local Docker daemon.
If `RESULT_FILE` is set, build metadata (status, ports, processes, timing) is written as JSON for downstream CI/CD steps.
### Build Cache Options
```bash Registry Cache
docker run --rm \
-v ./app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/my-app:latest \
-e CACHE_IMAGE=registry.io/my-app:cache \
miget/migetpacks:latest
```
```bash Shared Package Cache
docker run --rm \
-v ./app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /tmp/build-cache:/cache \
-e OUTPUT_IMAGE=my-app:local \
-e BUILD_CACHE_DIR=/cache \
miget/migetpacks:latest
```
```bash No Cache (Fresh Build)
docker run --rm \
-v ./app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
-e NO_CACHE=true \
miget/migetpacks:latest
```
## Runtime Container
The built container follows these conventions:
| Property | Value |
|----------|-------|
| User | `miget` (uid 1000) |
| Working directory | `/app` |
| Default port | `5000` |
| Process manager | None (single process) |
| Init system | None (PID 1 is your app) |
When `USE_DHI=true`, the runtime container is distroless (no shell, no package manager) and runs as the `nonroot` user instead.
## Next Steps
Full reference for all builder configuration options.
Optimize build times with registry and shared caches.
Combine multiple languages in a single build.
Use Docker Hardened Images for production.
---
url: https://migetpacks.com/introduction
---
---
title: "migetpacks"
description: "Zero-config container builds for any language"
---
Zero-config, secure, production-ready containers
Drop in your source code and get an optimized, non-root container image. No Dockerfile to write or maintain.
```bash
docker pull miget/migetpacks:latest
```
## Features
Auto-detects **14 languages** and their versions from your source code. No Dockerfile, no buildpacks config, no CI scripts to maintain.
Optional **Docker Hardened Images** (distroless), non-root runtime user, minimal attack surface, and no shell in production containers.
Optimized **layer caching**, registry mirrors, shared package manager caches, and BuildKit for parallel stage execution.
Native **Shipwright** integration, GitHub Actions support, result JSON for CI/CD pipelines, and multi-architecture builds.
## Why migetpacks
Unlike other build systems, migetpacks uses the same official Docker Hub images you already know (`node:20`, `python:3.12`, `ruby:3.2`), not custom stacks or proprietary base layers. This means:
- **Familiar images.** Your production container runs on the same upstream image you'd pick yourself.
- **Transparent builds.** migetpacks generates a real multi-stage Dockerfile you can read, debug, and take with you.
- **One flag for distroless.** Set `USE_DHI=true` to switch to Docker Hardened Images (dhi.io) with zero config changes.
## Quick Start
```bash
docker pull miget/migetpacks:latest
```
```bash
# From your project directory
docker run --rm \
-v $PWD:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
miget/migetpacks:latest
# Or with an explicit path
docker run --rm \
-v /path/to/your/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
miget/migetpacks:latest
```
```bash
docker run --rm -p 5000:5000 my-app:local
```
## Supported Languages
npm, yarn, pnpm
deno.json, deps.ts
bun.lockb, bunfig.toml
pip, uv, poetry
Bundler, Rails
go.mod, go.sum
Cargo.toml
Composer, FrankenPHP
Maven, Gradle
Gradle, Maven
sbt, Play Framework
Leiningen
C#, F#, ASP.NET
Mix, Phoenix
---
Already have a Dockerfile? migetpacks supports [custom Dockerfiles](/guides/custom-dockerfile) and [Docker Compose](/guides/docker-compose) projects too.
---
---
url: https://migetpacks.com/languages/bun
---
---
title: "Bun"
description: "Build and deploy Bun applications with optimized dependency caching."
---
## Detection
migetpacks detects Bun when any of these signals are present:
| Priority | Signal |
|----------|--------|
| 1 | `bun.lock` or `bun.lockb` (lockfiles) |
| 2 | `bunfig.toml` (config file) |
| 3 | `.bun-version` (version file) |
| 4 | `bun-types` in `package.json` dependencies |
Bun detection runs after backend frameworks (Ruby, Python, Go, etc.) but before Node.js. If your project has both a `bun.lock` and a `Gemfile`, it will be detected as Ruby with Bun available as a secondary buildpack.
## Version Detection
Bun version is resolved in this order:
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `BUN_VERSION` env var | `BUN_VERSION=1.3.4` |
| 2 | `.bun-version` file | `1.3.4` |
| 3 | `version` in `bunfig.toml` | `version = "1.3.4"` |
| 4 | Default | `1.3.4` |
## Build Process
migetpacks generates a multi-stage Dockerfile optimized for Bun:
```dockerfile
# Builder stage
FROM oven/bun:1.3.4 AS builder
WORKDIR /build
# Layer 1: Copy dependency files (cached if lockfile unchanged)
COPY package.json bun.lock ./
# Layer 2: Install dependencies
RUN bun install
# Layer 3: Copy source code
COPY . .
# Layer 4: Run build script (if present)
RUN bun run build
# Runtime stage
FROM oven/bun:1.3.4-slim
WORKDIR /app
COPY --from=builder /build /app
```
### Entry Point Detection
The entry point is automatically detected in this order:
1. `.ts`/`.js` file referenced in `scripts.start` of `package.json`
2. Common file patterns: `index.ts`, `server.ts`, `app.ts`, `main.ts`, `src/index.ts`, `src/server.ts`, `index.js`, `server.js`
3. Falls back to `index.ts`
### Build Script
If `package.json` contains a `"build"` script, `bun run build` is automatically executed after dependency installation. You can override this with the `BUILD_COMMAND` environment variable.
### Lockfile Format
Bun supports two lockfile formats:
- `bun.lock` (text-based, newer format)
- `bun.lockb` (binary, legacy format)
Both are automatically detected and copied during the build.
## Run Command
The default run command is determined in this order:
| Priority | Source | Command |
|----------|--------|---------|
| 1 | `RUN_COMMAND` env var | User-specified |
| 2 | `web:` in Procfile | From Procfile |
| 3 | Default | `bun run {entrypoint}` |
The entry point is detected automatically (see Entry Point Detection above).
## Caching
### Docker Layer Caching
Dependencies are installed in a separate layer before source code is copied. `bun install` is only re-run when `package.json` or `bun.lock`/`bun.lockb` changes.
### BuildKit Cache Mounts
When `BUILD_CACHE_DIR` is configured, BuildKit cache mounts are used:
| Cache Path | Contents |
|-----------|----------|
| `/cache/bun` | Bun install cache (`~/.bun/install/cache`) |
### Registry Cache
Use `CACHE_IMAGE` to push/pull BuildKit inline cache layers to a registry for cross-build caching.
## DHI Support
Bun is supported with Docker Hardened Images (requires version 1.3.6 or later).
| Stage | Image |
|-------|-------|
| Build | `dhi.io/bun:{version}-dev` |
| Runtime | `dhi.io/bun:{version}` |
DHI images for Bun are only available starting from version 1.3.6. If your project uses an older version, migetpacks will fall back to the standard `oven/bun` images.
The `-dev` variant includes a shell for building. The runtime image is distroless with no shell, providing a minimal attack surface.
## Example
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-bun-app:latest \
miget/migetpacks:latest
```
### With Custom Options
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/app:v1 \
-e BUN_VERSION=1.3.4 \
-e BUILD_COMMAND="bun run build:prod" \
-e USE_DHI=true \
miget/migetpacks:latest
```
---
url: https://migetpacks.com/languages/clojure
---
---
title: "Clojure"
description: "Build and deploy Clojure applications with migetpacks using Leiningen."
---
## Detection
migetpacks detects a Clojure application when either of the following files is present in the project root:
- `project.clj` (Leiningen)
- `deps.edn` (Clojure CLI/tools.deps)
## Version Detection
### Clojure Version
Clojure version is resolved in the following priority order:
| Source | Example |
|--------|---------|
| `.clojure-version` | `1.11.1` |
| `project.clj` (org.clojure/clojure dependency) | `[org.clojure/clojure "1.11.1"]` |
| `deps.edn` (org.clojure/clojure) | `org.clojure/clojure {:mvn/version "1.11.1"}` |
| Default | `1.11.1` |
### Leiningen Version
Leiningen version is detected from:
| Source | Example |
|--------|---------|
| `project.clj` (`:min-lein-version`) | `:min-lein-version "2.11.2"` |
| Default | `2.11.2` |
### Java Version
The JDK version used for compilation and runtime is read from:
| Source | Example |
|--------|---------|
| `system.properties` | `java.runtime.version=21` |
| Default | `21` |
## Build Process
migetpacks uses the official `clojure:temurin-{java}-lein` Docker image which includes Leiningen and the JDK:
```dockerfile
# Build stage
FROM clojure:temurin-21-lein AS builder
WORKDIR /build
# Clojure build environment
ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF-8"
ENV LEIN_ROOT=1
# Layer caching: copy project.clj first
COPY project.clj ./
# Download dependencies (cached if project.clj unchanged)
RUN lein deps
# Copy source code
COPY . .
# Build uberjar and cleanup
RUN lein uberjar \
&& rm -rf src/ test/ dev/ resources/public/js/compiled
# Runtime stage
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=builder --chown=1000:1000 /build /app
```
### Uberjar Detection
migetpacks detects custom uberjar names from `project.clj`:
```clojure
(defproject myapp "1.0.0"
:uberjar-name "myapp-standalone.jar"
...)
```
If `:uberjar-name` is specified, migetpacks uses it when configuring the run command.
## Run Command
The default run command for Clojure applications:
```bash
java -jar target/uberjar/*-standalone.jar
```
Override with `RUN_COMMAND` or a `Procfile`:
```text
web: java -jar target/uberjar/myapp-standalone.jar
```
### JVM Configuration
The following environment variables are set for production:
| Variable | Value |
|----------|-------|
| `JAVA_OPTS` | `-Dfile.encoding=UTF-8 -XX:MaxRAMPercentage=80.0 -Dclojure.main.report=stderr` |
| `JAVA_TOOL_OPTIONS` | `-Dfile.encoding=UTF-8` |
## Caching
### Layer Caching
Dependencies are cached by copying `project.clj` before the full source:
| Cached Files | Purpose |
|-------------|---------|
| `project.clj` | Leiningen project definition with dependencies |
| Scenario | Dependency Install |
|----------|-------------------|
| First build | Runs |
| Source code change only | **Cached** |
| project.clj change | Runs |
### BUILD_CACHE_DIR
When `BUILD_CACHE_DIR` is set, Leiningen and Maven caches persist at:
| Cache Path | Contents |
|------------|----------|
| lein cache | Leiningen profiles and plugins |
| m2 cache | Maven repository (`.m2`) |
## DHI Support
Docker Hardened Images are supported for Clojure runtime.
Clojure uses a hybrid approach: the official Clojure build image (which includes Leiningen) for building, and a DHI distroless image for runtime.
| Stage | Image |
|-------|-------|
| Build | `clojure:temurin-{java}-lein` (official) |
| Runtime | `dhi.io/eclipse-temurin:{java}` |
### DHI Notes
- The build image is always the official Clojure image (DHI does not provide Clojure/Leiningen images).
- Only the runtime stage uses DHI distroless Eclipse Temurin JRE.
- Java version is read from `system.properties` (default: 21).
- Runtime images are distroless (no shell).
### Example with DHI
```bash
docker run --rm \
-v /path/to/clojure-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-clojure-app:latest \
-e USE_DHI=true \
miget/migetpacks:latest
```
## Example
```bash
docker run --rm \
-v /path/to/clojure-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-clojure-app:latest \
miget/migetpacks:latest
```
### With custom JVM options
```bash
docker run --rm \
-v /path/to/clojure-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-clojure-app:latest \
-e JAVA_OPTS="-Xmx1g -Dclojure.main.report=stderr" \
miget/migetpacks:latest
```
---
url: https://migetpacks.com/languages/deno
---
---
title: "Deno"
description: "Build and deploy Deno applications with automatic entry point detection."
---
## Detection
migetpacks detects Deno when any of these files are present in your project root:
- `deno.json`
- `deno.jsonc`
- `deno.lock`
Deno detection takes priority over Node.js. If both `deno.json` and `package.json` exist, migetpacks will detect Deno as the primary language.
## Version Detection
Deno version is resolved in this order:
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `DENO_VERSION` env var | `DENO_VERSION=2.0` |
| 2 | `.deno-version` file | `2.6.1` |
| 3 | `.dvmrc` file | `2.6.1` |
| 4 | `version` field in `deno.json` | `"2.6.1"` |
| 5 | Default | `2.6.1` |
## Build Process
migetpacks generates a Dockerfile that caches dependencies separately from source code:
```dockerfile
FROM denoland/deno:2.6.1 AS builder
WORKDIR /build
# Set Deno cache inside build dir
ENV DENO_DIR=/build/.deno-cache
# Layer 1: Copy dependency files (cached if deno.json unchanged)
COPY deno.json deno.lock* ./
# Layer 2: Install dependencies
RUN deno install
# Layer 3: Copy source code
COPY . .
# Layer 4: Cache and compile entry point
RUN deno cache main.ts
# Runtime stage
FROM denoland/deno:2.6.1
WORKDIR /app
COPY --from=builder /build /app
ENV DENO_DIR=/app/.deno-cache
```
### Entry Point Detection
The entry point is automatically detected in this order:
1. `start` task in `deno.json` (extracts the `.ts`/`.js` file from the command)
2. Common file patterns: `main.ts`, `mod.ts`, `server.ts`, `app.ts`, `index.ts`, `index.js`, `main.js`, `src/main.ts`, `src/mod.ts`
3. Falls back to `main.ts`
### Dependency Caching
If `deno.json` contains an `imports` field, `deno install` is run in a separate layer before source code is copied. This ensures dependencies are cached when only source code changes.
## Run Command
The default run command is determined in this order:
| Priority | Source | Command |
|----------|--------|---------|
| 1 | `RUN_COMMAND` env var | User-specified |
| 2 | `web:` in Procfile | From Procfile |
| 3 | Default | `deno run --allow-net --allow-read --allow-env {entrypoint}` |
The entry point is detected automatically (see Entry Point Detection above).
## Caching
### Docker Layer Caching
Dependencies are installed from `deno.json` in a separate layer. The `deno cache` command pre-compiles the entry point and its dependencies, which are stored in `DENO_DIR` inside the build directory.
### Registry Cache
Use `CACHE_IMAGE` to push/pull BuildKit inline cache layers to a registry for cross-build caching.
Deno does not use BuildKit cache mounts since dependency caching is handled through the `DENO_DIR` environment variable and Docker layer caching.
## DHI Support
Deno is supported with Docker Hardened Images (requires version 2.6.4 or later).
| Stage | Image |
|-------|-------|
| Build | `dhi.io/deno:{version}-dev` |
| Runtime | `dhi.io/deno:{version}` |
DHI images for Deno are only available starting from version 2.6.4. If your project uses an older version, migetpacks will fall back to the standard `denoland/deno` images.
The DHI runtime image runs as the `deno` user (non-root) and is distroless with no shell.
## Example
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-deno-app:latest \
miget/migetpacks:latest
```
### With Custom Options
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/app:v1 \
-e DENO_VERSION=2.6.1 \
-e RUN_COMMAND="deno run --allow-all server.ts" \
-e USE_DHI=true \
miget/migetpacks:latest
```
---
url: https://migetpacks.com/languages/dotnet
---
---
title: ".NET"
description: "Build and deploy .NET/C# applications with migetpacks."
---
## Detection
migetpacks detects a .NET application when any of the following are present:
- `*.sln` or `*.slnx` (solution files, root level)
- `global.json` (root level)
- `*.csproj`, `*.fsproj`, or `*.vbproj` (project files, up to 2 levels deep)
## Version Detection
.NET SDK version is resolved in the following priority order:
| Source | Example |
|--------|---------|
| `global.json` (`sdk.version`) | `{"sdk": {"version": "8.0.100"}}` |
| `*.csproj` (`TargetFramework`) | `net8.0` |
| Default | `8.0` |
The `TargetFramework` value has the `net` prefix stripped (e.g., `net8.0` becomes `8.0`).
## Build Process
migetpacks generates a multi-stage Dockerfile using the official .NET SDK and ASP.NET runtime images:
```dockerfile
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS builder
WORKDIR /build
# .NET build optimizations
ENV NUGET_XMLDOC_MODE=skip
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
ENV DOTNET_NOLOGO=1
# Copy source code
COPY . .
# Restore, build, and publish
RUN dotnet restore \
&& dotnet publish --configuration Release --no-restore -p:PublishDir=bin/publish
# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=builder --chown=1000:1000 /build /app
```
### Source Code Cleanup
Since .NET is a compiled language, source code is completely removed from the runtime image after publishing:
| Removed | Reason |
|---------|--------|
| `*.cs`, `*.fs`, `*.vb` | Source files (compiled to DLLs) |
| `*.csproj`, `*.fsproj`, `*.vbproj` | Project files |
| `*.sln`, `*.slnx` | Solution files |
| `obj/` | Intermediate build files |
| `bin/Release/net*/`, `bin/Debug/` | Non-publish build output |
Only the `bin/publish/` directory contents are kept in the final image.
### .NET Tool Restore
If a `.config/dotnet-tools.json` is present, `dotnet tool restore` is run before the build to install any required .NET CLI tools.
## Run Command
The default run command for .NET applications:
```bash
dotnet MyApp.dll
```
migetpacks detects the main DLL from the publish output. Override with `RUN_COMMAND` or a `Procfile`:
```text
web: dotnet MyApp.dll --urls http://0.0.0.0:5000
```
### Runtime Configuration
The following environment variables are set for production:
| Variable | Value |
|----------|-------|
| `ASPNETCORE_ENVIRONMENT` | `Production` |
| `DOTNET_RUNNING_IN_CONTAINER` | `true` |
| `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT` | `1` |
| `DOTNET_CLI_TELEMETRY_OPTOUT` | `1` |
| `ASPNETCORE_HTTP_PORTS` | `5000` |
## Caching
### Layer Caching
The .NET build uses NuGet cache mounts for dependency caching:
| Scenario | Dependency Restore |
|----------|-------------------|
| First build | Runs |
| Source code change only | NuGet packages cached via mount |
| Project file change | Runs restore |
### BUILD_CACHE_DIR
When `BUILD_CACHE_DIR` is set, NuGet packages are cached via BuildKit mount at:
| Cache Path | Contents |
|------------|----------|
| NuGet packages mount | `~/.nuget/packages` |
## DHI Support
Docker Hardened Images are supported for .NET builds.
| Stage | Image |
|-------|-------|
| Build | `dhi.io/dotnet:{version}-sdk` |
| Runtime | `dhi.io/aspnetcore:{version}` |
### DHI Notes
- Build SDK images include the full .NET SDK for compilation.
- Runtime images are based on ASP.NET Core distroless images (no shell, no `apt-get`).
- The `nonroot` user is used instead of the `miget` user.
- Suitable for web APIs and ASP.NET Core applications.
### Example with DHI
```bash
docker run --rm \
-v /path/to/dotnet-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-dotnet-app:latest \
-e USE_DHI=true \
miget/migetpacks:latest
```
## Example
```bash
docker run --rm \
-v /path/to/dotnet-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-dotnet-app:latest \
miget/migetpacks:latest
```
### Monorepo with PROJECT_PATH
```bash
docker run --rm \
-v /path/to/monorepo:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-api:latest \
-e PROJECT_PATH=src/MyApi \
miget/migetpacks:latest
```
### With custom build command
```bash
docker run --rm \
-v /path/to/dotnet-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-dotnet-app:latest \
-e BUILD_COMMAND="dotnet publish -c Release -o /build/out" \
miget/migetpacks:latest
```
---
url: https://migetpacks.com/languages/elixir
---
---
title: "Elixir"
description: "Build and deploy Elixir and Phoenix applications with migetpacks."
---
## Detection
migetpacks detects an Elixir application when either of the following files is present in the project root:
- `mix.exs`
- `mix.lock`
## Version Detection
### Elixir Version
Elixir version is resolved in the following priority order:
| Source | Example |
|--------|---------|
| `.elixir-version` | `1.18.4` |
| `.tool-versions` (asdf) | `elixir 1.18.4` |
| Default | `1.18.4` |
### Erlang/OTP Version
Erlang version is resolved from:
| Source | Example |
|--------|---------|
| `.erlang-version` | `27.0` |
| `.tool-versions` (asdf) | `erlang 27.0` |
| Default | Auto-compatible with Elixir version |
If no Erlang version is specified, the Elixir Docker image's bundled Erlang/OTP version is used, which is guaranteed to be compatible.
## Build Process
migetpacks generates a multi-stage Dockerfile using official Elixir images:
```dockerfile
# Build stage
FROM elixir:1.18.4 AS builder
WORKDIR /build
# Elixir build environment
ENV MIX_ENV=prod
ENV ERL_FLAGS="-noinput"
# Layer caching: copy dependency files first
COPY mix.exs mix.lock* ./
COPY config ./config/
# Install Hex, Rebar, and fetch dependencies
RUN mix local.hex --force \
&& mix local.rebar --force \
&& mix deps.get --only prod
# Compile dependencies separately
RUN mix deps.compile
# Copy source code
COPY . .
# Compile and build release
RUN mix compile
RUN mix release
# Runtime stage
FROM elixir:1.18.4-slim
WORKDIR /app
COPY --from=builder --chown=1000:1000 /build /app
```
### Phoenix Framework Detection
If `mix.exs` contains a `:phoenix` dependency, migetpacks automatically:
1. Copies `config/` early (needed for dependency compilation)
2. Compiles assets using `mix assets.deploy` (or falls back to npm-based asset pipeline)
3. Runs `mix phx.digest` for static file fingerprinting
```dockerfile
# Phoenix asset compilation
RUN mix compile
RUN if [ -d "assets" ]; then \
mix assets.deploy 2>/dev/null || \
(cd assets && npm install && npm run deploy && cd .. && mix phx.digest); \
fi
```
### Umbrella Project Detection
If an `apps/` directory exists, migetpacks detects an umbrella project and copies child `mix.exs` files for proper dependency resolution.
### Mix Releases
migetpacks detects if the project uses [Mix releases](https://hexdocs.pm/mix/Mix.Tasks.Release.html) by checking for `releases:` in `mix.exs`. When releases are used:
- The release binary is self-contained (no Hex/Mix needed at runtime)
- The runtime image is slimmer
- The run command uses the release binary
When releases are **not** used:
- Hex and Mix are copied to the runtime image
- Git is installed at runtime (needed for git dependency verification)
- `mix phx.server` is used as the run command
## Run Command
The default run command depends on whether Mix releases are configured:
| Configuration | Default Command |
|--------------|----------------|
| With releases | `/app/bin/{app_name} start` |
| Without releases (Phoenix) | `mix phx.server` |
| Without releases (plain) | `mix run --no-halt` |
Override with `RUN_COMMAND` or a `Procfile`:
```text
web: /app/bin/myapp start
worker: /app/bin/myapp eval "MyApp.Worker.start()"
```
### Runtime Configuration
| Variable | Value |
|----------|-------|
| `MIX_ENV` | `prod` |
| `HOME` | `/home/miget` |
## Caching
### Layer Caching
Dependencies are cached by copying `mix.exs` and `mix.lock` before the full source:
| Cached Files | Purpose |
|-------------|---------|
| `mix.exs` | Project definition with dependencies |
| `mix.lock` | Locked dependency versions |
| `config/` | Configuration (Phoenix needs this for deps.compile) |
| Scenario | Dependency Install |
|----------|-------------------|
| First build | Runs |
| Source code change only | **Cached** |
| mix.exs/mix.lock change | Runs |
### BUILD_CACHE_DIR
When `BUILD_CACHE_DIR` is set, Hex and Mix dependency caches persist at:
| Cache Path | Contents |
|------------|----------|
| hex cache | Hex packages |
| mix cache | Mix dependencies and compiled artifacts |
## DHI Support
Docker Hardened Images (DHI) are **not supported** for Elixir. Official Elixir images are used for both build and runtime stages.
When `USE_DHI=true` is set, migetpacks will display a warning and continue using the official Elixir images:
| Stage | Image |
|-------|-------|
| Build | `elixir:{version}` |
| Runtime | `elixir:{version}-slim` |
## Example
```bash
docker run --rm \
-v /path/to/elixir-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-elixir-app:latest \
miget/migetpacks:latest
```
### Phoenix application
```bash
docker run --rm \
-v /path/to/phoenix-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-phoenix-app:latest \
miget/migetpacks:latest
```
### Running Phoenix
Phoenix requires `SECRET_KEY_BASE` at runtime. Generate one with `mix phx.gen.secret` or `openssl rand -hex 64`:
```bash
docker run -p 5000:5000 \
-e SECRET_KEY_BASE=$(openssl rand -hex 64) \
my-phoenix-app:latest
```
For production with a database:
```bash
docker run -p 5000:5000 \
-e SECRET_KEY_BASE=your-production-secret-key \
-e DATABASE_URL=ecto://user:pass@host/db \
my-phoenix-app:latest
```
### With build cache
```bash
docker run --rm \
-v /path/to/elixir-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /tmp/build-cache:/cache \
-e OUTPUT_IMAGE=my-elixir-app:latest \
-e BUILD_CACHE_DIR=/cache \
miget/migetpacks:latest
```
---
url: https://migetpacks.com/languages/go
---
---
title: "Go"
description: "Build and deploy Go applications as statically-linked binaries with distroless runtime."
---
## Detection
migetpacks detects Go when any of these files are present in your project root:
- `go.mod`
- `Godeps/Godeps.json`
- `glide.yaml`
## Version Detection
Go version is resolved in this order:
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `GO_VERSION` env var | `GO_VERSION=1.23` |
| 2 | `.go-version` file | `1.25.0` |
| 3 | `go` directive in `go.mod` | `go 1.23` |
| 4 | Default | `1.25.0` |
## Build Process
migetpacks generates a multi-stage Dockerfile that produces a static binary:
```dockerfile
# Builder stage
FROM golang:1.25 AS builder
WORKDIR /build
# Go build environment (static linking)
ENV CGO_ENABLED=0
ENV GOOS=linux
# Layer 1: Copy go.mod/go.sum for layer caching
COPY go.mod go.sum* ./
# Layer 2: Download dependencies (CACHED when go.mod unchanged)
RUN go mod download
# Layer 3: Copy source code
COPY . .
# Layer 4: Build static binary and cleanup
RUN go build -ldflags="-s -w" -o /build/myapp . \
&& rm -rf .git *.go go.mod go.sum vendor/
# Runtime stage
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates
WORKDIR /app
COPY --from=builder /build /app
```
### Static Linking
Go builds use `CGO_ENABLED=0` by default, producing fully static binaries that run on minimal base images. The linker flags `-s -w` strip debug information for smaller binaries.
### Module Name and Binary Detection
The binary name is automatically derived from the module name in `go.mod`:
- `module github.com/user/myapp` produces binary `myapp`
- Falls back to `app` if detection fails
### Package Spec
You can specify which packages to build using the `//+heroku install` comment in `go.mod`:
```go
module github.com/user/myapp
// +heroku install ./cmd/api ./cmd/worker
go 1.23
```
Without this directive, `go build .` is used (builds the package in the current directory).
For multiple packages, binaries are placed in `/build/bin/`:
```dockerfile
RUN go build -ldflags="-s -w" -o /build/bin/ ./cmd/...
```
### Build Hooks
migetpacks supports pre- and post-compile hooks:
- `bin/go-pre-compile` - Runs before `go build`
- `bin/go-post-compile` - Runs after `go build`
### golang-migrate
If `github.com/golang-migrate/migrate` is detected in `go.mod`, the `migrate` CLI tool is automatically installed in the builder stage.
### Linker Symbols
Set custom linker symbols using environment variables:
```bash
-e GO_LINKER_SYMBOL=main.version -e GO_LINKER_VALUE=1.0.0
```
This adds `-X main.version=1.0.0` to the linker flags.
## Run Command
The default run command is determined in this order:
| Priority | Source | Command |
|----------|--------|---------|
| 1 | `RUN_COMMAND` env var | User-specified |
| 2 | `web:` in Procfile | From Procfile |
| 3 | Default | `./app` (or `./myapp` based on module name) |
Binary names in Procfiles are automatically prefixed with `./` if they don't already have a path prefix.
## Caching
### Docker Layer Caching
Dependencies are downloaded in a separate layer. `go mod download` is only re-run when `go.mod` or `go.sum` changes.
### BuildKit Cache Mounts
When `BUILD_CACHE_DIR` is configured, BuildKit cache mounts are used:
| Cache Path | Contents |
|-----------|----------|
| `/cache/go` | Go module cache (`/go/pkg/mod`) and build cache (`~/.cache/go-build`) |
### Registry Cache
Use `CACHE_IMAGE` to push/pull BuildKit inline cache layers to a registry for cross-build caching.
## DHI Support
Go is supported with Docker Hardened Images (requires version 1.22 or later).
| Stage | Image |
|-------|-------|
| Build | `dhi.io/golang:{version}-dev` |
| Runtime | `dhi.io/golang:{version}` |
DHI images for Go require minimum patch versions: 1.22.12, 1.23.7, or 1.24.1+. Older patch versions within these minor releases are not available.
The DHI runtime image is distroless with no shell. Since Go produces static binaries, this works without any special handling.
### Go Toolchain in Runtime
If you need the Go toolchain available at runtime (for plugins, `go run`, etc.):
```bash
-e GO_INSTALL_TOOLS_IN_IMAGE=true
```
This copies `/usr/local/go` from the builder to the runtime image.
## Example
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-go-app:latest \
miget/migetpacks:latest
```
### With Custom Options
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/app:v1 \
-e GO_VERSION=1.23 \
-e GO_LINKER_SYMBOL=main.version \
-e GO_LINKER_VALUE=1.2.3 \
-e USE_DHI=true \
miget/migetpacks:latest
```
### Multi-Binary Project
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/app:v1 \
-e RUN_COMMAND="./bin/api" \
miget/migetpacks:latest
```
With `// +heroku install ./cmd/api ./cmd/worker` in `go.mod`, both binaries are available at `/app/bin/api` and `/app/bin/worker`.
---
url: https://migetpacks.com/languages/java
---
---
title: "Java"
description: "Build and deploy Java applications with migetpacks using Maven or Gradle."
---
## Detection
migetpacks detects a Java application when any of the following files are present in the project root:
- `pom.xml` (Maven)
- `build.gradle` (Gradle, Groovy DSL)
- `build.gradle.kts` (Gradle, Kotlin DSL)
## Version Detection
Java version is resolved in the following priority order:
| Source | Example |
|--------|---------|
| `.java-version` | `21` |
| `system.properties` | `java.runtime.version=21` |
| `pom.xml` (`` or ``) | `21` |
| Default | `21` |
### Version Normalization
Legacy version formats are normalized for Docker image compatibility:
| Input | Normalized |
|-------|-----------|
| `1.8` | `8` |
| `1.11` | `11` |
| `17` | `17` |
| `21` | `21` |
## Build Process
### Build Tool Detection
migetpacks automatically detects the build tool:
| File Present | Build Tool | Build Image |
|-------------|-----------|-------------|
| `pom.xml` | Maven | `maven:3-eclipse-temurin-{version}` |
| `build.gradle` or `build.gradle.kts` | Gradle | `gradle:jdk{version}` |
### Gradle Wrapper
If `gradlew` is present in the project root, migetpacks uses the Gradle Wrapper instead of the system Gradle for correct version compatibility.
### Generated Dockerfile
```dockerfile
# Build stage
FROM maven:3-eclipse-temurin-21 AS builder
WORKDIR /build
# Layer caching: copy build files first
COPY pom.xml ./
# Download dependencies (cached if pom.xml unchanged)
RUN mvn dependency:go-offline -B || true
# Copy source code
COPY . .
# Build and cleanup source
RUN mvn package -DskipTests -B \
&& rm -rf src/ .mvn/ mvnw* pom.xml
# Runtime stage
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=builder --chown=1000:1000 /build /app
```
For Gradle projects:
```dockerfile
FROM gradle:jdk21 AS builder
WORKDIR /build
COPY build.gradle* settings.gradle* ./
RUN gradle dependencies --no-daemon || true
COPY . .
RUN gradle build -x test --no-daemon \
&& rm -rf src/ gradle/ gradlew* build.gradle*
```
## Run Command
The default run command depends on the build tool output:
```bash
java -jar target/*.jar
```
Override with `RUN_COMMAND` or a `Procfile`:
```text
web: java -jar target/myapp-1.0.jar
```
### JVM Configuration
The following environment variables are set for production:
| Variable | Value |
|----------|-------|
| `JAVA_OPTS` | `-Dfile.encoding=UTF-8 -XX:MaxRAMPercentage=80.0` |
| `JAVA_TOOL_OPTIONS` | `-Dfile.encoding=UTF-8` |
## Caching
### Layer Caching
Dependencies are cached by copying build definition files before the full source:
| Build Tool | Cached Files |
|-----------|-------------|
| Maven | `pom.xml` |
| Gradle | `build.gradle*`, `settings.gradle*`, `gradlew`, `gradle/` |
| Scenario | Dependency Install |
|----------|-------------------|
| First build | Runs |
| Source code change only | **Cached** |
| Build file change | Runs |
### BUILD_CACHE_DIR
When `BUILD_CACHE_DIR` is set, build tool caches persist at:
| Cache Path | Contents |
|------------|----------|
| `/cache/maven` | Maven repository (`~/.m2/repository`) |
| `/cache/gradle` | Gradle caches and wrapper |
## DHI Support
Docker Hardened Images are supported for Java builds.
| Stage | Image |
|-------|-------|
| Build | `dhi.io/eclipse-temurin:{version}-jdk-dev` |
| Runtime | `dhi.io/eclipse-temurin:{version}` |
### DHI Notes
- Eclipse Temurin DHI images do **not** include Maven or Gradle. Build tools are installed via `apt-get` in the builder stage.
- Runtime images are distroless (no shell, no `apt-get`).
- The `nonroot` user is used instead of the `miget` user.
- Version normalization applies: `1.8` becomes `8`, `1.11` becomes `11`.
### Example with DHI
```bash
docker run --rm \
-v /path/to/java-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-java-app:latest \
-e USE_DHI=true \
miget/migetpacks:latest
```
## Example
```bash
docker run --rm \
-v /path/to/java-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-java-app:latest \
miget/migetpacks:latest
```
### With build cache
```bash
docker run --rm \
-v /path/to/java-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /tmp/build-cache:/cache \
-e OUTPUT_IMAGE=my-java-app:latest \
-e BUILD_CACHE_DIR=/cache \
miget/migetpacks:latest
```
---
url: https://migetpacks.com/languages/kotlin
---
---
title: "Kotlin"
description: "Build and deploy Kotlin applications with migetpacks using Gradle."
---
## Detection
migetpacks detects a Kotlin application when any of the following conditions are met:
- `settings.gradle.kts` exists
- `.kt` files exist in `src/` directory
- `build.gradle.kts` contains the `kotlin` plugin
- `build.gradle` (Groovy DSL) contains a Kotlin plugin reference
Kotlin detection takes priority over Java/Gradle detection. If your project has `build.gradle.kts` with Kotlin plugins, it will be identified as Kotlin rather than Java.
## Version Detection
### Kotlin Version
Kotlin version is resolved in the following priority order:
| Source | Example |
|--------|---------|
| `.kotlin-version` | `2.1.0` |
| `gradle.properties` (`kotlin.version`) | `kotlin.version=2.1.0` |
| `build.gradle.kts` (plugin version) | `kotlin("jvm") version "2.1.0"` |
| Default | `2.1.0` |
### Java Version
The JDK version used for compilation and runtime is read from:
| Source | Example |
|--------|---------|
| `system.properties` | `java.runtime.version=21` |
| Default | `21` |
## Build Process
migetpacks generates a multi-stage Dockerfile using Eclipse Temurin JDK with Gradle:
```dockerfile
# Build stage
FROM gradle:jdk21 AS builder
WORKDIR /build
# Kotlin build environment
ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF-8"
# Layer caching: copy Gradle build files first
COPY gradlew ./
COPY gradle/ gradle/
COPY build.gradle* settings.gradle* ./
# Download dependencies (cached if build files unchanged)
RUN ./gradlew dependencies --no-daemon || true
# Copy source code
COPY . .
# Build and cleanup
RUN ./gradlew build -x test --no-daemon \
&& rm -rf src/ gradle/ gradlew* build.gradle* settings.gradle*
# Runtime stage
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=builder --chown=1000:1000 /build /app
```
### Gradle Wrapper
If `gradlew` is present in the project root, migetpacks uses the Gradle Wrapper for correct version compatibility. Otherwise, it falls back to the system Gradle.
## Run Command
The default run command for Kotlin applications:
```bash
java -jar build/libs/*.jar
```
Override with `RUN_COMMAND` or a `Procfile`:
```text
web: java -jar build/libs/myapp-1.0-all.jar
```
### JVM Configuration
The following environment variables are set for production:
| Variable | Value |
|----------|-------|
| `JAVA_OPTS` | `-Dfile.encoding=UTF-8 -XX:MaxRAMPercentage=80.0` |
| `JAVA_TOOL_OPTIONS` | `-Dfile.encoding=UTF-8` |
## Caching
### Layer Caching
Dependencies are cached by copying Gradle build files before the full source:
| Cached Files | Purpose |
|-------------|---------|
| `build.gradle.kts` / `build.gradle` | Build configuration |
| `settings.gradle.kts` / `settings.gradle` | Project settings |
| `gradlew` + `gradle/` | Gradle Wrapper |
| Scenario | Dependency Install |
|----------|-------------------|
| First build | Runs |
| Source code change only | **Cached** |
| Build file change | Runs |
### BUILD_CACHE_DIR
When `BUILD_CACHE_DIR` is set, Gradle caches persist at:
| Cache Path | Contents |
|------------|----------|
| `/cache/gradle` | Gradle caches and wrapper downloads |
## DHI Support
Docker Hardened Images are supported for Kotlin builds.
| Stage | Image |
|-------|-------|
| Build | `dhi.io/eclipse-temurin:21-jdk-dev` |
| Runtime | `dhi.io/eclipse-temurin:21` |
### DHI Notes
- Java version is read from `system.properties` (`java.runtime.version`), defaulting to 21.
- Eclipse Temurin DHI JDK images include the JDK but Gradle must be available (via wrapper or system install).
- Runtime images are distroless (no shell).
- The `nonroot` user is used instead of the `miget` user.
### Example with DHI
```bash
docker run --rm \
-v /path/to/kotlin-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-kotlin-app:latest \
-e USE_DHI=true \
miget/migetpacks:latest
```
## Example
```bash
docker run --rm \
-v /path/to/kotlin-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-kotlin-app:latest \
miget/migetpacks:latest
```
### With build cache
```bash
docker run --rm \
-v /path/to/kotlin-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /tmp/build-cache:/cache \
-e OUTPUT_IMAGE=my-kotlin-app:latest \
-e BUILD_CACHE_DIR=/cache \
miget/migetpacks:latest
```
---
url: https://migetpacks.com/languages/nodejs
---
---
title: "Node.js"
description: "Build and deploy Node.js applications with automatic package manager detection."
---
## Detection
migetpacks detects Node.js when any of these files are present in your project root:
- `package.json`
- `package-lock.json`
- `yarn.lock`
- `pnpm-lock.yaml`
### Package Manager Detection
The package manager is detected from lockfiles first, then from the `packageManager` field in `package.json`:
| Priority | Signal | Result |
|----------|--------|--------|
| 1 | `pnpm-lock.yaml` exists | pnpm |
| 2 | `yarn.lock` exists | yarn |
| 3 | `package-lock.json` exists | npm |
| 4 | `packageManager` field in `package.json` | npm, yarn, or pnpm |
| 5 | `package.json` exists (no lockfile) | npm |
If multiple lockfiles are detected (e.g., both `yarn.lock` and `package-lock.json`), migetpacks will warn you and use the highest-priority lockfile. Remove unused lockfiles to avoid confusion.
## Version Detection
Node.js version is resolved in this order:
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `NODE_VERSION` env var | `NODE_VERSION=20` |
| 2 | `.node-version` file | `22.16.0` |
| 3 | `.nvmrc` file | `20` |
| 4 | `engines.node` in `package.json` | `">=20"` |
| 5 | Default | `22.16.0` |
Version constraints are normalized for Docker compatibility:
- `20.x` becomes `20`
- `>=18` becomes `18`
- `^20.0.0` becomes `20.0`
- `20.x || 22.x || 24.x` becomes `24` (highest major)
## Build Process
migetpacks generates a multi-stage Dockerfile optimized for layer caching:
```dockerfile
# Builder stage
FROM node:22 AS builder
WORKDIR /build
# Layer 1: Copy dependency files (cached if lockfiles unchanged)
COPY package.json package-lock.json ./
ENV NODE_ENV=production
# Layer 2: Install dependencies (CACHED when only source code changes)
RUN npm ci
# Layer 3: Copy source code
COPY . .
# Layer 4: Build and prune
RUN npm run build \
&& npm prune --production \
&& rm -rf .git .github test tests spec __tests__ coverage .nyc_output .cache
# Runtime stage
FROM node:22-slim
WORKDIR /app
COPY --from=builder /build /app
ENV NODE_ENV=production
```
### Package Manager Commands
| Manager | Install | Prune |
|---------|---------|-------|
| npm | `npm ci` (or `npm install`) | `npm prune --production` |
| yarn | `yarn install --frozen-lockfile` | `yarn install --production --frozen-lockfile --ignore-scripts` |
| pnpm | `pnpm install --frozen-lockfile` | `pnpm prune --prod` |
### Build Script
If `package.json` contains a `"build"` script, it is automatically executed after dependency installation. You can override this with the `BUILD_COMMAND` environment variable.
### Runtime Cleanup
The following directories are removed from the final image:
- `.git/`, `.github/`
- `test/`, `tests/`, `spec/`, `__tests__/`
- `coverage/`, `.nyc_output/`, `.cache/`
## Run Command
The default run command is determined in this order:
| Priority | Source | Command |
|----------|--------|---------|
| 1 | `RUN_COMMAND` env var | User-specified |
| 2 | `web:` in Procfile | From Procfile |
| 3 | `scripts.start` in `package.json` | `npm start` |
| 4 | Default | `node index.js` |
## Caching
### Docker Layer Caching
Dependencies are installed in a separate layer before source code is copied. This means `npm ci` is only re-run when `package.json` or `package-lock.json` changes.
### BuildKit Cache Mounts
When `BUILD_CACHE_DIR` is configured, BuildKit cache mounts are used for package manager caches:
| Manager | Cache Path |
|---------|-----------|
| npm | `/cache/npm` |
| yarn | `/cache/yarn` |
| pnpm | `/cache/pnpm` |
### Registry Cache
Use `CACHE_IMAGE` to push/pull BuildKit inline cache layers to a registry for cross-build caching.
## DHI Support
Node.js is fully supported with Docker Hardened Images.
| Stage | Image |
|-------|-------|
| Build | `dhi.io/node:{version}-dev` |
| Runtime | `dhi.io/node:{version}` |
The `-dev` variant includes a shell and package manager for building. The runtime image is distroless with no shell, providing a minimal attack surface.
DHI runtime images run as the `node` user (non-root). Commands must use exec format since there is no `/bin/sh` available.
## Example
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-node-app:latest \
miget/migetpacks:latest
```
### With Custom Options
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/app:v1 \
-e NODE_VERSION=20 \
-e BUILD_COMMAND="npm run build:prod" \
-e NODE_OPTIONS="--max-old-space-size=4096" \
-e USE_DHI=true \
miget/migetpacks:latest
```
### Environment Variables
Any environment variable not recognized by migetpacks is automatically injected into the generated Dockerfile. Common Node.js variables:
- `NODE_OPTIONS` - Node.js runtime options (e.g., `--max-old-space-size=4096`)
- `VITE_*` - Vite build-time variables
- `NEXT_PUBLIC_*` - Next.js public environment variables
---
url: https://migetpacks.com/languages/php
---
---
title: "PHP"
description: "Build and deploy PHP applications with migetpacks using FrankenPHP."
---
## Detection
migetpacks detects a PHP application when any of the following files are present in the project root:
- `composer.json`
- `index.php`
## Version Detection
PHP version is resolved in the following priority order:
| Source | Example |
|--------|---------|
| `.php-version` | `8.3` |
| `composer.json` (`require.php`) | `"php": ">=8.2"` |
| Default | `8.3` |
Version constraints from `composer.json` (e.g., `>=8.2`, `^8.3`) are normalized to Docker-compatible versions.
FrankenPHP supports PHP 8.2, 8.3, and 8.4. Older versions will fall back to PHP 8.2 with a warning.
## Build Process
migetpacks generates a multi-stage Dockerfile using [FrankenPHP](https://frankenphp.dev/) (a modern PHP application server built on Go and Caddy):
```dockerfile
# Build stage
FROM dunglas/frankenphp:builder-php8.3 AS builder
WORKDIR /build
# Install PHP extensions detected from composer.json
RUN apt-get update && apt-get install -y ... \
&& docker-php-ext-install pdo_pgsql gd ...
# Layer caching: copy dependency files first
COPY composer.json composer.lock* ./
# Install Composer and dependencies (cached if lockfiles unchanged)
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
&& composer install --no-dev --optimize-autoloader --no-scripts
# Copy source code
COPY . .
# Runtime stage
FROM dunglas/frankenphp:php8.3
WORKDIR /app
COPY --from=builder --chown=1000:1000 /build /app
```
### PHP Extension Detection
Extensions declared in `composer.json` under `require` with the `ext-` prefix are automatically installed:
```json
{
"require": {
"ext-pdo_pgsql": "*",
"ext-gd": "*",
"ext-intl": "*"
}
}
```
### Web Root Detection
migetpacks automatically detects the web root directory by checking for these directories (in order):
1. `web/`
2. `public/`
3. `html/`
4. `www/`
5. `.` (project root, fallback)
## Run Command
The default run command uses FrankenPHP with Caddy:
```bash
frankenphp run --config /etc/caddy/Caddyfile
```
Override with `RUN_COMMAND` or a `Procfile`:
```text
web: frankenphp run --config /etc/caddy/Caddyfile
```
### PHP Configuration
Production PHP settings are automatically configured via environment variables:
| Setting | Default |
|---------|---------|
| `memory_limit` | `256M` |
| `max_execution_time` | `30` |
| `upload_max_filesize` | `32M` |
| `opcache.enable` | `1` |
| `display_errors` | `Off` |
| `session.cookie_secure` | `1` |
These can be overridden at runtime by setting the corresponding `PHP_INI_*` environment variable (e.g., `PHP_INI_MEMORY_LIMIT=512M`).
## Caching
### Layer Caching
Dependencies are cached by copying `composer.json` and `composer.lock` before the full source:
| Scenario | Dependency Install |
|----------|-------------------|
| First build | Runs |
| Source code change only | **Cached** |
| Lockfile change | Runs |
### BUILD_CACHE_DIR
When `BUILD_CACHE_DIR` is set, Composer packages are cached at:
| Cache Path | Contents |
|------------|----------|
| `/cache/composer` | Composer package cache |
## DHI Support
Docker Hardened Images (DHI) are **not supported** for PHP. FrankenPHP is a specialized application server that requires its own base image.
When `USE_DHI=true` is set, migetpacks will continue to use the standard FrankenPHP images.
## Example
```bash
docker run --rm \
-v /path/to/php-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-php-app:latest \
miget/migetpacks:latest
```
### With custom environment variables
```bash
docker run --rm \
-v /path/to/php-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-php-app:latest \
-e PHP_INI_MEMORY_LIMIT=512M \
-e APP_ENV=production \
miget/migetpacks:latest
```
---
url: https://migetpacks.com/languages/python
---
---
title: "Python"
description: "Build and deploy Python applications with pip or uv, including Django and FastAPI support."
---
## Detection
migetpacks detects Python when any of these files are present in your project root:
- `requirements.txt`
- `setup.py`
- `Pipfile`
- `pyproject.toml`
## Version Detection
Python version is resolved in this order:
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `PYTHON_VERSION` env var | `PYTHON_VERSION=3.12` |
| 2 | `.python-version` file | `3.12.8` |
| 3 | `runtime.txt` file | `python-3.12.8` |
| 4 | `requires-python` in `uv.lock` | `requires-python = ">=3.13"` |
| 5 | `requires-python` in `pyproject.toml` | `requires-python = ">=3.12"` |
| 6 | Default | `3.12.8` |
Version constraints are normalized for Docker compatibility (e.g., `>=3.13` becomes `3.13`).
## Build Process
migetpacks generates a multi-stage Dockerfile with virtualenv isolation:
### Standard pip/pipenv/poetry
```dockerfile
# Builder stage
FROM python:3.12 AS builder
WORKDIR /build
# Layer 1: Copy dependency files
COPY requirements.txt* pyproject.toml* poetry.lock* Pipfile* ./
ENV PYTHONUNBUFFERED=1
# Layer 2: Create virtualenv
RUN python -m venv /opt/venv \
&& /opt/venv/bin/pip install --upgrade pip
# Layer 3: Install dependencies (CACHED when only source code changes)
RUN . /opt/venv/bin/activate \
&& pip install -r requirements.txt
# Layer 4: Copy source code
COPY . .
# Layer 5: Collectstatic and cleanup
RUN . /opt/venv/bin/activate \
&& python manage.py collectstatic --noinput \
&& rm -rf .git test tests __pycache__ .pytest_cache .coverage htmlcov
# Runtime stage
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /opt/venv /opt/venv
COPY --from=builder /build /app
ENV PATH="/opt/venv/bin:$PATH"
ENV PYTHONUNBUFFERED=1
```
### uv Projects
When `uv.lock` is present, migetpacks uses the `uv` package manager natively:
```dockerfile
# Builder stage
FROM python:3.13 AS builder
WORKDIR /build
COPY requirements.txt* pyproject.toml* uv.lock* ./
ENV PYTHONUNBUFFERED=1
# Install uv and sync dependencies
RUN pip install uv \
&& uv venv /app/.venv \
&& UV_PROJECT_ENVIRONMENT=/app/.venv uv sync --locked --no-dev
COPY . .
# Runtime stage
FROM python:3.13-slim
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /build /app
ENV PATH="/app/.venv/bin:$PATH"
```
### Package Manager Detection
| File | Package Manager |
|------|-----------------|
| `uv.lock` | uv (native) |
| `requirements.txt` | pip |
| `Pipfile` / `Pipfile.lock` | pipenv |
| `poetry.lock` | poetry |
| `pyproject.toml` (no lock) | pip |
### Dependency Detection
migetpacks automatically detects database and XML libraries from your dependency files and installs the required system packages:
| Python Package | System Package |
|---------------|---------------|
| `psycopg`, `asyncpg`, `aiopg` | `libpq5` |
| `mysqlclient`, `pymysql`, `aiomysql` | `libmariadb3` |
| `aiosqlite` | `libsqlite3-0` |
| `lxml` | `libxml2`, `libxslt1.1` |
### Runtime Cleanup
The following are removed from the final image:
- `.git/`, `.github/`
- `test/`, `tests/`
- `__pycache__/`, `.pytest_cache/`
- `.coverage`, `htmlcov/`
### Django Support
If `manage.py` is detected, `python manage.py collectstatic --noinput` is automatically run during the build.
## Run Command
The default run command is determined in this order:
| Priority | Source | Command |
|----------|--------|---------|
| 1 | `RUN_COMMAND` env var | User-specified |
| 2 | `web:` in Procfile | From Procfile |
| 3 | Django + gunicorn | `gunicorn project.wsgi:application --bind 0.0.0.0:$PORT` |
| 4 | Django + uvicorn | `uvicorn project.asgi:application --host 0.0.0.0 --port $PORT` |
| 5 | gunicorn detected | `gunicorn app:app --bind 0.0.0.0:$PORT` |
| 6 | uvicorn detected | `uvicorn main:app --host 0.0.0.0 --port $PORT` |
| 7 | `manage.py` exists | `python manage.py runserver 0.0.0.0:$PORT` |
| 8 | Default | `python app.py` or `python main.py` |
## Caching
### Docker Layer Caching
Dependencies are installed in a separate layer using a virtualenv. `pip install` is only re-run when dependency files change.
### BuildKit Cache Mounts
When `BUILD_CACHE_DIR` is configured, BuildKit cache mounts are used:
| Cache Path | Contents |
|-----------|----------|
| `/cache/pip` | pip wheel cache |
| `/cache/uv` | uv package cache |
### Registry Cache
Use `CACHE_IMAGE` to push/pull BuildKit inline cache layers to a registry for cross-build caching.
## DHI Support
Python is fully supported with Docker Hardened Images.
| Stage | Image |
|-------|-------|
| Build | `dhi.io/python:{version}-dev` |
| Runtime | `dhi.io/python:{version}` |
The `-dev` variant includes a shell and build tools. The runtime image is distroless with no shell, providing a minimal attack surface.
The virtualenv is copied from the builder stage to the distroless runtime, ensuring all installed packages are available without needing pip in the runtime image.
## Example
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-python-app:latest \
miget/migetpacks:latest
```
### With Custom Options
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/app:v1 \
-e PYTHON_VERSION=3.13 \
-e RUN_COMMAND="gunicorn myapp.wsgi:application --bind 0.0.0.0:5000" \
-e USE_DHI=true \
miget/migetpacks:latest
```
---
url: https://migetpacks.com/languages/ruby
---
---
title: "Ruby"
description: "Build and deploy Ruby and Rails applications with native gem support and asset precompilation."
---
## Detection
migetpacks detects Ruby when any of these files are present in your project root:
- `Gemfile`
- `Rakefile`
- `config.ru`
## Version Detection
Ruby version is resolved in this order:
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `RUBY_VERSION` env var | `RUBY_VERSION=3.3` |
| 2 | `.ruby-version` file | `3.4.4` |
| 3 | `ruby` declaration in `Gemfile` | `ruby "3.3.0"` |
| 4 | Default | `3.4.4` |
The `ruby-` prefix is automatically stripped (e.g., `ruby-3.2.0` becomes `3.2.0`).
## Build Process
migetpacks generates a multi-stage Dockerfile with bundler layer caching:
```dockerfile
# Builder stage
FROM ruby:3.4 AS builder
WORKDIR /build
# Layer 1: Copy Gemfile for layer caching
COPY Gemfile Gemfile.lock ./
# Layer 2: Configure and install gems (CACHED when Gemfile unchanged)
ENV GEM_HOME=/build/vendor/bundle
ENV BUNDLE_PATH=/build/vendor/bundle
RUN gem install bundler -v '2.5.0' --no-document && \
bundle config set --local path '/build/vendor/bundle' && \
bundle config set --local deployment 'true' && \
bundle install --jobs 4 --retry 3
# Layer 3: Copy source code
COPY . .
# Layer 4: Rails asset precompilation and cleanup
ENV RAILS_ENV=production
ENV SECRET_KEY_BASE_DUMMY=1
RUN bundle exec rake assets:precompile && \
bundle exec rake assets:clean && \
rm -rf .git test spec vendor/bundle/ruby/*/cache
# Runtime stage
FROM ruby:3.4-slim
RUN apt-get update && apt-get install -y libpq5 tzdata ca-certificates
WORKDIR /app
COPY --from=builder /build /app
```
### Dependency Detection
migetpacks inspects your `Gemfile` and `Gemfile.lock` to install the correct system libraries:
| Gem | Build Package | Runtime Package |
|-----|--------------|----------------|
| `pg` | `libpq-dev` | `libpq5` |
| `mysql2`, `trilogy` | `libmariadb-dev` | `libmariadb3` |
| `sqlite3` | `libsqlite3-dev` | `libsqlite3-0` |
| `nokogiri`, `rails` | `libxml2-dev`, `libxslt1-dev` | `libxml2`, `libxslt1.1` |
Additionally, `tzdata` and `ca-certificates` are always installed in the runtime image.
### Bundler Version
The bundler version is automatically detected from `Gemfile.lock` (the `BUNDLED WITH` section) and installed in the builder stage.
### Local Gems
If your `Gemfile` references local gems with `path:` options, those directories are automatically copied before `bundle install` to ensure they are available during gem resolution.
### Rails Asset Precompilation
If your project has a `Rakefile` and either `app/assets/` or `app/javascript/` directories, migetpacks automatically:
1. Sets `RAILS_ENV=production`, `SECRET_KEY_BASE_DUMMY=1`, and a dummy `DATABASE_URL`
2. Runs `bundle exec rake assets:precompile`
3. Runs `bundle exec rake assets:clean`
### Runtime Cleanup
The following are removed from the final image:
- `.git/`, `.github/`
- `test/`, `tests/`, `spec/`, `features/`
- `.rspec`, `.rubocop*`
- `vendor/bundle/ruby/*/cache/`
## Run Command
The default run command is determined in this order:
| Priority | Source | Command |
|----------|--------|---------|
| 1 | `RUN_COMMAND` env var | User-specified |
| 2 | `web:` in Procfile | From Procfile |
| 3 | `config.ru` exists | `bundle exec puma -C config/puma.rb` |
| 4 | Default | `bundle exec ruby app.rb` |
### Runtime Environment
The following environment variables are set in the runtime container:
```bash
RAILS_ENV=production
RACK_ENV=production
RAILS_SERVE_STATIC_FILES=enabled
RAILS_LOG_TO_STDOUT=enabled
MALLOC_ARENA_MAX=2
DISABLE_SPRING=1
PUMA_PERSISTENT_TIMEOUT=95
```
## Caching
### Docker Layer Caching
Gems are installed in a separate layer before source code is copied. `bundle install` is only re-run when `Gemfile` or `Gemfile.lock` changes.
### BuildKit Cache Mounts
When `BUILD_CACHE_DIR` is configured, BuildKit cache mounts are used:
| Cache Path | Contents |
|-----------|----------|
| `/cache/bundler` | Bundler gem cache |
### Registry Cache
Use `CACHE_IMAGE` to push/pull BuildKit inline cache layers to a registry for cross-build caching.
## DHI Support
Ruby is fully supported with Docker Hardened Images.
| Stage | Image |
|-------|-------|
| Build | `dhi.io/ruby:{version}-dev` |
| Runtime | `dhi.io/ruby:{version}` |
### DHI-Specific Behavior
The DHI runtime is distroless (no shell, no apt-get), which requires special handling:
**Native gem libraries** are copied from the builder stage:
```dockerfile
COPY --from=builder /usr/lib/*/libpq.so* /usr/lib/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
```
**Process commands are transformed** since shebangs do not work in distroless:
- `./bin/rails server` becomes `ruby bin/rails server`
- `bundle exec` commands work because bundler is installed as a gem
**BUNDLER_VERSION** is explicitly set in the environment to prevent bundler from attempting auto-switching (which fails without `/usr/bin/env`).
Shell-dependent features like `.profile.d` scripts and shell variable expansion (`${PORT:-5000}`) are not available in DHI runtime images. Use environment variables directly.
## Multi-Buildpack
Ruby applications commonly use Node.js for asset compilation. When `package.json` is detected alongside a Ruby project, Node.js is automatically added as a secondary buildpack:
```bash
# Explicit buildpack ordering
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-rails-app:latest \
-e BUILDPACKS=ruby,nodejs \
miget/migetpacks:latest
```
See the [Multi-Buildpack guide](/guides/multi-buildpack) for more details.
## Example
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-ruby-app:latest \
miget/migetpacks:latest
```
### With Custom Options
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/app:v1 \
-e RUBY_VERSION=3.3 \
-e RAILS_MASTER_KEY=abc123 \
-e BUNDLE_WITHOUT="development test" \
-e USE_DHI=true \
miget/migetpacks:latest
```
---
url: https://migetpacks.com/languages/rust
---
---
title: "Rust"
description: "Build and deploy Rust applications with optimized dependency caching and release binaries."
---
## Detection
migetpacks detects Rust when any of these files are present in your project root:
- `Cargo.toml`
- `Cargo.lock`
## Version Detection
Rust version is resolved in this order:
| Priority | Source | Example |
|----------|--------|---------|
| 1 | `RUSTUP_TOOLCHAIN` env var | `RUSTUP_TOOLCHAIN=1.80.0` |
| 2 | `rust-toolchain` file | `1.92.0` |
| 3 | `channel` in `rust-toolchain.toml` | `channel = "1.92.0"` |
| 4 | Default | `1.92.0` |
## Build Process
migetpacks generates a multi-stage Dockerfile with an optimized dependency caching strategy:
```dockerfile
# Builder stage
FROM rust:1.92 AS builder
WORKDIR /build
# Rust build environment
ENV CARGO_HOME=/usr/local/cargo
ENV RUSTUP_HOME=/usr/local/rustup
# Layer 1: Copy dependency manifests
COPY Cargo.toml Cargo.lock* ./
# Layer 2: Create dummy source and build deps (CACHED when Cargo.toml unchanged)
RUN mkdir -p src && echo 'fn main() {}' > src/main.rs
RUN cargo build --release && rm -rf src
# Layer 3: Copy real source code
COPY . .
# Layer 4: Touch main.rs and rebuild (only app code recompiles)
RUN touch src/main.rs
RUN cargo build --release \
&& cp target/release/myapp /build/app \
&& rm -rf src target Cargo.toml Cargo.lock .git
# Runtime stage
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates
WORKDIR /app
COPY --from=builder /build /app
ENV RUST_BACKTRACE=1
ENV RUST_LOG=info
```
### Dependency Caching Strategy
Rust's dependency caching uses a "dummy source" technique:
1. Copy `Cargo.toml` and `Cargo.lock`
2. Create a minimal `fn main() {}` source file
3. Run `cargo build --release` to compile all dependencies
4. Remove the dummy source
5. Copy the real source code
6. Touch `src/main.rs` to invalidate only the application crate
7. Rebuild -- only the application code recompiles
This ensures that dependency compilation is cached across builds when only source code changes.
### Binary Detection
The binary name is automatically detected from `Cargo.toml`:
1. `[[bin]]` section `name` field
2. `[package]` section `name` field
3. Falls back to `app`
### Workspace Projects
For workspace projects (when `[workspace]` is present in `Cargo.toml`), all workspace member `Cargo.toml` files are copied for proper dependency resolution.
### Static Assets
If your project contains `static/`, `public/`, `assets/`, `templates/`, `dist/`, or `build/` directories, they are automatically copied alongside the binary to the runtime image.
## Run Command
The default run command is determined in this order:
| Priority | Source | Command |
|----------|--------|---------|
| 1 | `RUN_COMMAND` env var | User-specified |
| 2 | `web:` in Procfile | From Procfile |
| 3 | Default | `./app` |
Procfile commands referencing `./target/release/binary` are automatically translated to `./app` since the binary is copied to the application root during build.
### Runtime Environment
The following environment variables are set in the runtime container:
```bash
RUST_BACKTRACE=1
RUST_LOG=info
```
## Caching
### Docker Layer Caching
The dummy-source technique ensures that `cargo build --release` for dependencies is cached when only source code changes. A full dependency rebuild only happens when `Cargo.toml` or `Cargo.lock` is modified.
### BuildKit Cache Mounts
When `BUILD_CACHE_DIR` is configured, BuildKit cache mounts are used:
| Cache Path | Contents |
|-----------|----------|
| `/cache/cargo` | Cargo registry (`/usr/local/cargo/registry`) and build artifacts (`/build/target`) |
### Registry Cache
Use `CACHE_IMAGE` to push/pull BuildKit inline cache layers to a registry for cross-build caching.
## DHI Support
Rust is fully supported with Docker Hardened Images.
| Stage | Image |
|-------|-------|
| Build | `dhi.io/rust:{version}-dev` |
| Runtime | `dhi.io/rust:{version}` |
The `-dev` variant includes a shell and build tools (cargo, rustc). The runtime image is distroless with no shell, providing a minimal attack surface.
Since Rust produces static binaries, the distroless runtime works without any special handling.
## Example
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-rust-app:latest \
miget/migetpacks:latest
```
### With Custom Options
```bash
docker run --rm \
-v /path/to/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/app:v1 \
-e RUSTUP_TOOLCHAIN=1.80.0 \
-e BUILD_COMMAND="cargo build --release --features production" \
-e USE_DHI=true \
miget/migetpacks:latest
```
### Workspace Project
```bash
docker run --rm \
-v /path/to/workspace:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=registry.io/api:v1 \
-e RUN_COMMAND="./app" \
miget/migetpacks:latest
```
---
url: https://migetpacks.com/languages/scala
---
---
title: "Scala"
description: "Build and deploy Scala applications with migetpacks using sbt."
---
## Detection
migetpacks detects a Scala application when any of the following are present in the project root:
- `build.sbt`
- `build.sc`
- `project/` directory
## Version Detection
### Scala Version
Scala version is resolved in the following priority order:
| Source | Example |
|--------|---------|
| `.scala-version` | `3.5.2` |
| `build.sbt` (`scalaVersion`) | `scalaVersion := "3.5.2"` |
| `project/build.properties` (`scala.version`) | `scala.version=3.5.2` |
| Default | `3.5.2` |
### sbt Version
sbt version is detected from:
| Source | Example |
|--------|---------|
| `project/build.properties` | `sbt.version=1.10.6` |
| Default | `1.10.6` |
sbt 0.13.x is deprecated and will show a warning. sbt versions below 0.13.18 are not supported. sbt 2.x support is experimental.
### Java Version
The JDK version used for compilation and runtime is read from:
| Source | Example |
|--------|---------|
| `system.properties` | `java.runtime.version=21` |
| Default | `21` |
Supported Java versions: 8, 11, 17, 21.
## Build Process
migetpacks uses the official `sbtscala/scala-sbt` Docker image which includes sbt, Scala, and the JDK:
```dockerfile
# Build stage
FROM sbtscala/scala-sbt:eclipse-temurin-21.0.6_7_1.11.7_3.5.2 AS builder
WORKDIR /build
# Scala/sbt build environment
ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF-8"
ENV SBT_OPTS="-Xmx2G -XX:MaxMetaspaceSize=512m"
# Layer caching: copy sbt build files first
COPY build.sbt ./
COPY project/ project/
# Download dependencies (cached if build files unchanged)
RUN sbt update || true
# Copy source code
COPY . .
# Build and cleanup
RUN sbt compile assembly \
&& rm -rf src/ project/ build.sbt
# Runtime stage
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=builder --chown=1000:1000 /build /app
```
### Framework Detection
migetpacks detects the application type and adjusts the build accordingly:
| Detection | Build Command | Output |
|-----------|--------------|--------|
| Play Framework (`PlayScala`/`PlayJava` in `build.sbt`) | `sbt compile stage` | `target/universal/stage/` |
| sbt-native-packager (in `project/plugins.sbt`) | `sbt compile stage` | `target/universal/stage/` |
| Standard sbt project | `sbt compile assembly` | `target/scala-*/app.jar` |
## Run Command
The default run command depends on the project type:
| Project Type | Default Command |
|-------------|----------------|
| Standard (assembly) | `java -jar target/scala-*/app.jar` |
| Play Framework | `target/universal/stage/bin/{app-name}` |
| sbt-native-packager | `target/universal/stage/bin/{app-name}` |
Override with `RUN_COMMAND` or a `Procfile`:
```text
web: java -jar target/scala-3.5.2/myapp-assembly-1.0.jar
```
### JVM Configuration
The following environment variables are set for production:
| Variable | Value |
|----------|-------|
| `JAVA_OPTS` | `-Dfile.encoding=UTF-8 -XX:MaxRAMPercentage=80.0` |
| `JAVA_TOOL_OPTIONS` | `-Dfile.encoding=UTF-8` |
## Caching
### Layer Caching
Dependencies are cached by copying sbt build definition files before the full source:
| Cached Files | Purpose |
|-------------|---------|
| `build.sbt` | Project build definition |
| `project/` | sbt plugins, build properties, Scala version |
| Scenario | Dependency Install |
|----------|-------------------|
| First build | Runs |
| Source code change only | **Cached** |
| Build file change | Runs |
### BUILD_CACHE_DIR
When `BUILD_CACHE_DIR` is set, sbt and dependency caches persist at:
| Cache Path | Contents |
|------------|----------|
| sbt cache | sbt boot and global settings |
| ivy cache | Ivy repository (legacy dependencies) |
| coursier cache | Coursier downloads (modern dependencies) |
## DHI Support
Docker Hardened Images are supported for Scala runtime.
Scala uses a hybrid approach: the official `sbtscala` build image (which includes sbt tools) for building, and a DHI distroless image for runtime.
| Stage | Image |
|-------|-------|
| Build | `sbtscala/scala-sbt:eclipse-temurin-{jdk}_{sbt}_{scala}` (official) |
| Runtime | `dhi.io/eclipse-temurin:{java}` |
### DHI Notes
- The build image is always the official sbtscala image (DHI does not provide sbt images).
- Only the runtime stage uses DHI distroless Eclipse Temurin JRE.
- Java version is read from `system.properties` (default: 21).
- Runtime images are distroless (no shell).
### Example with DHI
```bash
docker run --rm \
-v /path/to/scala-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-scala-app:latest \
-e USE_DHI=true \
miget/migetpacks:latest
```
## Example
```bash
docker run --rm \
-v /path/to/scala-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-scala-app:latest \
miget/migetpacks:latest
```
### Play Framework app
```bash
docker run --rm \
-v /path/to/play-app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-play-app:latest \
-e RUN_COMMAND="target/universal/stage/bin/myapp -Dhttp.port=5000" \
miget/migetpacks:latest
```
---
url: https://migetpacks.com/quickstart
---
---
title: "Quickstart"
description: "Build your first container in 3 commands"
---
## Prerequisites
You need Docker installed and running on your machine. Any version with BuildKit support (18.09+) works.
## Build Your First Container
Download the latest migetpacks image from Docker Hub:
```bash
docker pull miget/migetpacks:latest
```
Point migetpacks at your application source code. It will auto-detect the language, version, and build configuration:
```bash
docker run --rm \
-v /path/to/your/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
miget/migetpacks:latest
```
Replace `/path/to/your/app` with the absolute path to your project directory.
When `OUTPUT_IMAGE` does not contain a registry prefix (e.g., `my-app:local` instead of `registry.io/my-app:latest`), the image stays on your local Docker daemon and is not pushed anywhere.
Run your newly built container:
```bash
docker run --rm -p 5000:5000 my-app:local
```
Open [http://localhost:5000](http://localhost:5000) in your browser to see your app running.
## What Happened?
When you ran migetpacks, it performed four steps automatically:
Scanned your source code for language markers (`package.json`, `Gemfile`, `go.mod`, `requirements.txt`, etc.) and identified the runtime.
Read version files (`.nvmrc`, `.ruby-version`, `.python-version`, `go.mod`) and lock files to determine the exact runtime version.
Generated an optimized multi-stage Dockerfile with dependency layer caching, build commands, and a minimal runtime stage.
Used Docker-in-Docker with BuildKit to build the image and load it into your local Docker daemon.
## Common Options
```bash Specify Language
docker run --rm \
-v /path/to/your/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
-e LANGUAGE=ruby \
miget/migetpacks:latest
```
```bash Custom Port
docker run --rm \
-v /path/to/your/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
-e PORT=3000 \
miget/migetpacks:latest
```
```bash Custom Run Command
docker run --rm \
-v /path/to/your/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
-e RUN_COMMAND="bundle exec puma -C config/puma.rb" \
miget/migetpacks:latest
```
```bash Hardened Images (DHI)
docker run --rm \
-v /path/to/your/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-e OUTPUT_IMAGE=my-app:local \
-e USE_DHI=true \
miget/migetpacks:latest
```
## Push to a Registry
To push the built image to a remote registry, use a fully-qualified image name and mount your Docker credentials:
```bash
docker run --rm \
-v /path/to/your/app:/workspace/source \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $HOME/.docker/config.json:/root/.docker/config.json:ro \
-e OUTPUT_IMAGE=registry.io/myorg/my-app:latest \
miget/migetpacks:latest
```
## Next Steps
Understand the detection, generation, and build pipeline in detail.
Full reference for all configuration options.
Set up migetpacks in your CI/CD pipeline.
Use distroless, CVE-free base images for production.
---
url: https://migetpacks.com/reference/architecture
---
---
title: "Architecture"
description: "How migetpacks builds containers"
---
## Overview
migetpacks is a containerized build runtime that detects your application's language, generates an optimized multi-stage Dockerfile, and builds a production-ready container image. It runs entirely inside a Docker container using Docker-in-Docker (DinD).
## Build Flow
The build process follows a linear pipeline:
```text
detect → detect-version → generate Dockerfile → buildx build → push
```
1. **Detect** (`bin/detect`) -- Identifies the programming language from source files
2. **Detect Version** (`bin/detect-version`) -- Reads version from `.node-version`, `.nvmrc`, `.ruby-version`, `package.json`, etc.
3. **Generate Dockerfile** (`bin/build`) -- Creates a multi-stage Dockerfile using the language-specific library
4. **Build** -- Runs `docker buildx build` with BuildKit features
5. **Push** -- Pushes the built image to the target registry (or loads locally)
## Docker-in-Docker (DinD)
migetpacks starts a Docker daemon (`dockerd`) inside its own container. This provides:
- Isolation from the host Docker daemon
- Control over BuildKit configuration
- Registry mirror configuration
- Custom storage driver options
### Daemon Configuration
The internal Docker daemon is configured with:
```json
{
"storage-driver": "overlay2",
"registry-mirrors": ["https://your-mirror.io"],
"features": {
"buildkit": true
}
}
```
### MTU Configuration
The Docker network MTU is lowered by 50 bytes from the host MTU to accommodate overlay network encapsulation headers:
```text
Host MTU: 1500
DinD MTU: 1450
```
This prevents packet fragmentation when running inside Kubernetes overlay networks (Flannel, Calico, Weave).
### Storage Driver
The default storage driver is `overlay2`. For nested DinD environments where `overlay2` is not available, use `fuse-overlayfs`:
```bash
STORAGE_DRIVER=fuse-overlayfs
```
## Multi-Stage Builds
Every generated Dockerfile uses multi-stage builds to separate build dependencies from the runtime image:
```dockerfile
# Stage 1: Builder (has compilers, dev tools, build deps)
FROM node:20 AS builder
WORKDIR /build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --production
# Stage 2: Runtime (minimal, no build tools)
FROM node:20-slim
WORKDIR /app
COPY --from=builder /build ./
USER miget
EXPOSE 5000
CMD ["node", "server.js"]
```
**Benefits:**
- Runtime image contains only production dependencies
- Build tools and intermediate files are not shipped
- Smaller image size and reduced attack surface
## BuildKit Features
migetpacks leverages BuildKit for advanced build capabilities:
### Cache Mounts
Package manager caches are mounted as BuildKit cache volumes, persisting across builds:
```dockerfile
RUN --mount=type=cache,target=/root/.npm,sharing=shared \
npm ci
```
This avoids re-downloading packages when only source code changes.
### Inline Cache
When `CACHE_IMAGE` is set, BuildKit exports cache metadata inline with the image, enabling cross-machine cache reuse:
```bash
CACHE_IMAGE=registry.io/myapp:cache
CACHE_MODE=max # Cache all layers (default: min, final layer only)
```
### Additional Cache Sources
Use `CACHE_FROM` to specify additional read-only cache sources:
```bash
CACHE_FROM=registry.io/myapp:main-cache,registry.io/myapp:develop-cache
```
These are skipped when `NO_CACHE=true`.
## Layer Caching Strategy
Dockerfiles are generated with optimized layer ordering to maximize cache hits:
```dockerfile
# Layer 1: Dependency manifest (changes rarely)
COPY package.json package-lock.json ./
# Layer 2: Install dependencies (CACHED if manifest unchanged)
RUN npm ci
# Layer 3: Source code (changes frequently)
COPY . .
# Layer 4: Build (runs when source changes)
RUN npm run build
```
| Scenario | Layer 2 (install) | Layer 4 (build) |
|----------|-------------------|-----------------|
| First build | Runs | Runs |
| Source change only | **Cached** | Runs |
| Lockfile change | Runs | Runs |
This optimization means dependency installation is skipped for most iterative builds, saving significant time.
## Registry Mirror
When `REGISTRY_MIRROR` is set, it is configured in the Docker daemon's `registry-mirrors` list. All image pulls automatically try the mirror first before falling back to Docker Hub:
```json
{
"registry-mirrors": ["https://registry.example.io/mirror"]
}
```
This is particularly effective for:
- Avoiding Docker Hub rate limits
- Reducing pull times in private networks
- Caching base images locally (e.g., Harbor pull-through proxy)
## Security Model
### Non-Root Runtime
All generated containers run as a non-root user (`miget` uid 1000, or `nonroot` for DHI). This prevents privilege escalation and meets Kubernetes Pod Security Standards.
### Read-Only Source Directories
Source code is mounted read-only (`readOnly: true`) when possible. migetpacks copies source to a working directory before building, so the original source is never modified.
### Credential Filtering
The `KNOWN_BUILDER_VARS` list in `bin/build` ensures sensitive environment variables are never written to the generated Dockerfile:
- `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` -- never exposed
- `DOCKER_CONFIG` -- not written to image
- Builder-internal variables -- filtered out
Only unknown environment variables (user-defined) are injected as `ENV` statements in the Dockerfile. This enables passing build-time configuration like `NODE_OPTIONS` or `VITE_API_URL` while keeping secrets safe.
### Image Provenance
BuildKit records build provenance metadata, including:
- Source commit information (when available)
- Build timestamps
- Builder image version
## Component Reference
| Component | Path | Purpose |
|-----------|------|---------|
| Language detection | `bin/detect` | Identifies language from source files |
| Version detection | `bin/detect-version` | Reads version from config files |
| Main build orchestrator | `bin/build` | Generates Dockerfile, runs buildx |
| Release metadata | `bin/release` | Parses Procfile, outputs process types |
| Common helpers | `lib/common.sh` | Output formatting, cache ID generation |
| Multi-buildpack | `lib/buildpacks.sh` | Multiple language runtime support |
| Node.js | `lib/nodejs.sh` | Node.js/npm/yarn/pnpm builds |
| Python | `lib/python.sh` | Python/pip/uv builds |
| Ruby | `lib/ruby.sh` | Ruby/Bundler/Rails builds |
| Go | `lib/go.sh` | Go module builds |
| Rust | `lib/rust.sh` | Rust/Cargo builds |
| Java | `lib/java.sh` | Java/Maven/Gradle builds |
| Kotlin | `lib/kotlin.sh` | Kotlin/Gradle builds |
| Scala | `lib/scala.sh` | Scala/sbt builds |
| Clojure | `lib/clojure.sh` | Clojure/Leiningen builds |
| .NET | `lib/dotnet.sh` | .NET/C# builds |
| PHP | `lib/php.sh` | PHP/Composer/FrankenPHP builds |
| Elixir | `lib/elixir.sh` | Elixir/Phoenix builds |
| Deno | `lib/deno.sh` | Deno builds |
| Bun | `lib/bun.sh` | Bun builds |
| Entrypoint | `bin/entrypoint.sh` | Container entrypoint (starts dockerd) |
---
url: https://migetpacks.com/reference/result-json
---
---
title: "Result JSON"
description: "Build output schema reference"
---
## Overview
When `RESULT_FILE` is set, migetpacks writes a JSON file containing build metadata after every build. This file is used by post-build steps (e.g., deployment pipelines, notification services) to determine build status and access image information.
The builder always exits with code 0 to ensure post-build steps (like Shipwright callbacks or sidecar containers) always execute. Check the `status` field in the result JSON to determine if the build actually succeeded or failed.
## Schema
| Field | Type | Description |
|-------|------|-------------|
| `status` | `"success"` or `"failed"` | Build outcome |
| `exit_code` | number | Docker build exit code (0 on success) |
| `error` | string | Error message (only present on failure) |
| `images` | array | Built images with detected ports |
| `processes` | object | Process types from Procfile |
| `env` | object | Environment variables from app.json |
| `formation` | array | Process formation from app.json |
| `scripts` | array | Deployment scripts from app.json |
| `addons` | array | Service dependencies from app.json |
| `build_time` | number | Build duration in seconds |
| `language` | string | Detected or specified language |
| `timestamp` | string | ISO 8601 build completion time |
| `_shell` | boolean | `false` for distroless/DHI containers (no shell available) |
| `build` | object | Git provenance and builder facts (commit, branch, description, repository, build time, builder version) |
### images
```json
{
"images": [
{
"name": "registry.io/your-org/your-app:latest",
"ports": ["5000/tcp"]
}
]
}
```
For Docker Compose builds, multiple images are listed:
```json
{
"images": [
{"name": "registry.io/myapp-api:latest", "ports": ["5000/tcp"]},
{"name": "registry.io/myapp-web:latest", "ports": ["3000/tcp"]}
]
}
```
### processes
Parsed from the application's Procfile:
```json
{
"processes": {
"web": "bundle exec puma -C config/puma.rb",
"release": "bundle exec rails db:migrate",
"workers": [
{"name": "sidekiq", "command": "bundle exec sidekiq"},
{"name": "clock", "command": "bundle exec clockwork clock.rb"}
]
}
}
```
- `web` -- the primary web process (from `RUN_COMMAND`, Procfile `web:`, or language default)
- `release` -- one-time release command (e.g., database migrations)
- `workers` -- background worker processes (all non-web, non-release Procfile entries)
### env
Environment variables defined in `app.json`:
```json
{
"env": {
"SECRET_KEY_BASE": "a1b2c3d4e5f6...",
"RAILS_ENV": "production",
"DATABASE_URL": null
}
}
```
- Variables with `"generator": "secret"` have auto-generated values
- Variables with `"value": "..."` use the specified value
- Variables with `"required": true` are `null` (must be provided at deploy time)
### formation
Process configuration from `app.json`:
```json
{
"formation": [
{"name": "web", "quantity": 2, "size": "standard-1x"},
{"name": "worker", "quantity": 1, "size": null}
]
}
```
### scripts
Deployment scripts from `app.json`:
```json
{
"scripts": [
{"name": "postdeploy", "command": "bundle exec rails db:migrate"},
{"name": "pr-predestroy", "command": "bundle exec rails db:drop"}
]
}
```
### addons
Service dependencies from `app.json`:
```json
{
"addons": [
{"plan": "heroku-postgresql:hobby-dev", "as": null, "options": null},
{"plan": "heroku-redis:premium-0", "as": "CACHE", "options": null},
{"plan": "mongolab:shared-single-small", "as": "MONGO", "options": {"version": "5.0"}}
]
}
```
- `plan` -- the addon plan identifier
- `as` -- attachment alias (overrides config var prefix), or `null`
- `options` -- provider-specific options object, or `null`
### _shell
Indicates whether the runtime container has a shell available:
```json
{
"_shell": false
}
```
When `_shell` is `false` (DHI/distroless images), deployment systems should:
- Use exec format for commands (no `/bin/sh -c` wrapper)
- Pre-expand shell variables like `${PORT:-5000}`
- Avoid relying on shebangs in scripts
### build
Git provenance and builder facts, present on every build:
```json
{
"build": {
"commit": "9f3c1a7b8e2d4f5a6c7b8e2d4f5a6c7b8e2d4f5a",
"commit_short": "9f3c1a7",
"branch": "main",
"description": "Fix build-vars call order",
"committed_at": "2026-06-17T19:40:00Z",
"built_at": "2026-06-17T20:00:00Z",
"builder_version": "0.0.264",
"language": "ruby",
"repository": "https://github.com/acme/my-api"
}
}
```
Unknown fields are omitted. Values resolve by precedence: the `build-meta`
input envelope, then `MIGET_GIT_*` / `SOURCE_VERSION` environment variables,
then `.git` autodetection of the source directory.
## Success Example
A complete result file for a successful Ruby build:
```json
{
"status": "success",
"exit_code": 0,
"images": [
{
"name": "registry.io/your-org/your-app:latest",
"ports": ["5000/tcp"]
}
],
"processes": {
"web": "bundle exec puma -C config/puma.rb",
"release": "bundle exec rails db:migrate",
"workers": [
{"name": "sidekiq", "command": "bundle exec sidekiq"}
]
},
"env": {
"SECRET_KEY_BASE": "a1b2c3d4e5f6789...",
"RAILS_ENV": "production",
"DATABASE_URL": null
},
"formation": [
{"name": "web", "quantity": 2, "size": "standard-1x"},
{"name": "worker", "quantity": 1, "size": null}
],
"scripts": [
{"name": "postdeploy", "command": "bundle exec rails db:migrate"}
],
"addons": [
{"plan": "heroku-postgresql:hobby-dev", "as": null, "options": null}
],
"build_time": 45,
"language": "ruby",
"timestamp": "2025-12-18T20:00:00Z"
}
```
## Failure Example
A result file for a failed build:
```json
{
"status": "failed",
"exit_code": 1,
"error": "Docker build failed: npm ERR! Missing: peer dependency react@^18.0.0",
"images": [],
"processes": {},
"build_time": 12,
"language": "nodejs",
"timestamp": "2025-12-18T20:01:00Z"
}
```
## Usage
### Reading in Shell Scripts
```bash
#!/bin/bash
RESULT_FILE="/workspace/output/build-result.json"
# Wait for result file
while [ ! -f "$RESULT_FILE" ]; do sleep 2; done
# Check status
STATUS=$(jq -r '.status' "$RESULT_FILE")
if [ "$STATUS" = "failed" ]; then
ERROR=$(jq -r '.error' "$RESULT_FILE")
echo "Build failed: $ERROR"
exit 1
fi
# Get image name
IMAGE=$(jq -r '.images[0].name' "$RESULT_FILE")
echo "Built image: $IMAGE"
```
### Reading in Python
```python
import json
import time
from pathlib import Path
result_path = Path("/workspace/output/build-result.json")
# Wait for result
while not result_path.exists():
time.sleep(2)
result = json.loads(result_path.read_text())
if result["status"] == "failed":
raise Exception(f"Build failed: {result['error']}")
image = result["images"][0]["name"]
ports = result["images"][0]["ports"]
print(f"Built {image} exposing {ports}")
```
### Shipwright Post-Build Step
```yaml
- name: deploy
image: your-deployer:latest
volumeMounts:
- name: results
mountPath: /workspace/output
env:
- name: RESULT_FILE
value: /workspace/output/build-result.json
```
---
url: https://migetpacks.com/reference/runtime-container
---
---
title: "Runtime Container"
description: "Built container configuration"
---
## Overview
migetpacks produces optimized runtime containers that run as non-root users by default. The container configuration depends on whether you are using standard images or Docker Hardened Images (DHI).
## Standard Runtime
When `USE_DHI=false` (default), migetpacks generates containers with the following configuration:
| Property | Value |
|----------|-------|
| User | `miget` (uid: 1000) |
| Group | `miget` (gid: 1000) |
| Working directory | `/app` |
| Exposed port | `5000` (configurable via `PORT`) |
| Shell | Available (`/bin/sh`) |
### Container Structure
```text
/app/ # Application source and dependencies
/app/node_modules/ # Node.js dependencies (if applicable)
/app/vendor/bundle/ # Ruby gems (if applicable)
/app/.venv/ # Python virtualenv (if applicable)
```
### User Configuration
The `miget` user is created in the runtime image:
```dockerfile
RUN groupadd -g 1000 miget && \
useradd -u 1000 -g miget -m miget
USER miget
WORKDIR /app
```
### Port Configuration
The default exposed port is `5000`. Override it with the `PORT` environment variable:
```bash
docker run --rm \
-e PORT=3000 \
-e OUTPUT_IMAGE=my-app:local \
miget/migetpacks:latest
```
The `PORT` environment variable is set in the generated Dockerfile, so the application can read it at runtime.
## DHI Runtime (Distroless)
When `USE_DHI=true`, migetpacks uses Docker Hardened Images from dhi.io. These are distroless containers with a minimal attack surface.
| Property | Value |
|----------|-------|
| User | `nonroot` (uid varies by image) |
| Working directory | `/app` |
| Exposed port | `5000` (configurable via `PORT`) |
| Shell | **Not available** |
| Package manager | **Not available** |
### Distroless Constraints
DHI runtime images have no shell, no package manager, and no common utilities. This means:
1. **No shell commands** -- Commands cannot use `/bin/sh -c` wrappers
2. **Exec format only** -- Process commands must use exec format
3. **No shebangs** -- Scripts like `./bin/rails` must be invoked as `ruby bin/rails`
4. **No variable expansion** -- Shell variables like `${PORT:-5000}` must be pre-expanded
5. **No debugging tools** -- No `ls`, `cat`, `curl`, etc. in production
### Command Transformation
migetpacks automatically transforms commands for distroless:
| Original | Transformed |
|----------|-------------|
| `./bin/rails server` | `ruby bin/rails server` |
| `bundle exec puma` | `ruby -S bundle exec puma` |
| `${PORT:-5000}` | Pre-expanded to actual port value |
### Result JSON Indicator
When DHI is used, the result JSON includes `"_shell": false` to signal that the container is distroless. Deployment systems should use this flag to determine how to execute commands in the container.
## Runtime Cleanup
migetpacks automatically removes unnecessary files from runtime images to reduce size and attack surface.
### Node.js
| Removed | Description |
|---------|-------------|
| `.git/` | Git history |
| `.github/` | GitHub configuration |
| `test/`, `tests/` | Test files |
| `spec/` | Spec files |
| `__tests__/` | Jest tests |
| `coverage/` | Coverage reports |
| `.nyc_output/` | NYC coverage data |
| `.cache/` | Build cache |
### Ruby
| Removed | Description |
|---------|-------------|
| `.git/` | Git history |
| `.github/` | GitHub configuration |
| `test/`, `tests/` | Test files |
| `spec/` | RSpec files |
| `features/` | Cucumber features |
| `.rspec` | RSpec config |
| `.rubocop*` | Rubocop config |
| `vendor/bundle/*/cache/` | Gem cache |
### Python
| Removed | Description |
|---------|-------------|
| `.git/` | Git history |
| `.github/` | GitHub configuration |
| `test/`, `tests/` | Test files |
| `__pycache__/` | Bytecode cache |
| `.pytest_cache/` | Pytest cache |
| `.coverage` | Coverage data |
| `htmlcov/` | Coverage HTML |
### .NET
| Removed | Description |
|---------|-------------|
| `.git/` | Git history |
| `.github/` | GitHub configuration |
| `*.cs` | C# source files |
| `*.csproj` | Project files |
| `*.sln`, `*.slnx` | Solution files |
| `obj/` | Intermediate build output |
| `bin/Release/net*/` | Additional framework outputs |
| `bin/Debug/` | Debug builds |
.NET is a compiled language, so source code is completely removed from the runtime image. Node.js, Ruby, and Python are interpreted languages, so source code is kept but development/test files are removed.
## Inspecting Built Containers
### List Files
```bash
docker run --rm my-app:local ls -la /app/
```
### Check Environment
```bash
docker run --rm my-app:local env
```
### Check User
```bash
docker run --rm my-app:local id
# uid=1000(miget) gid=1000(miget) groups=1000(miget)
```
### Check Exposed Ports
```bash
docker inspect my-app:local --format='{{json .Config.ExposedPorts}}'
# {"5000/tcp":{}}
```
### Check Entrypoint/CMD
```bash
docker inspect my-app:local --format='{{json .Config.Cmd}}'
# ["node","server.js"]
```
### Layer History
```bash
docker history my-app:local
```
### Image Size
```bash
docker images my-app:local --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
```
## Security
### Non-Root by Default
All migetpacks containers run as a non-root user. This provides:
- Process isolation from the host
- Prevention of privilege escalation
- Compliance with Kubernetes Pod Security Standards (restricted profile)
### Read-Only Filesystem
For maximum security, run the container with a read-only root filesystem:
```bash
docker run --rm \
--read-only \
--tmpfs /tmp \
-p 5000:5000 \
my-app:local
```
### No Credentials in Image
migetpacks never includes AWS credentials or other secrets in the built image. The `KNOWN_BUILDER_VARS` filter ensures sensitive variables (like `AWS_ACCESS_KEY_ID`) are excluded from the generated Dockerfile.