---
url: /templates/create-your-own.md
description: Build a custom application template for DockIY.
---

# Create your own template

DockIY templates are ordinary Git repositories with a `dockiy.yml`, a Docker
build, a Compose file, and deployment hooks. Start from one of the existing
[templates](/templates/vitepress), or create a repository with this minimum
structure:

```text
.
├── Dockerfile
├── docker-compose.yml
├── dockiy.yml
└── scripts/
    ├── build.sh
    └── start.sh
```

## Define `dockiy.yml`

Put `dockiy.yml` at the repository root. A single-environment application can
use `default`:

```yaml
name: my-app

images:
  - app

environments:
  default:
    host: app.example.com
    compose_file: docker-compose.yml

hooks:
  build: scripts/build.sh
  start: scripts/start.sh
  healthcheck: scripts/healthcheck.sh
```

For separate staging and production deployments, define exactly these two
environments instead:

```yaml
environments:
  staging:
    host: staging.example.com
    compose_file: docker-compose.yml
    secrets_file: .enc.staging.env
  production:
    host: app.example.com
    compose_file: docker-compose.yml
    secrets_file: .enc.production.env
```

Important rules:

* `name` and image names must start with a lowercase letter and contain only
  lowercase letters, numbers, `_`, or `-`.
* Omit `images` only when the build produces one image named `app`.
* `build` and `start` hooks are required. `pre_start`,
  `rollback_pre_start`, and `healthcheck` are optional.
* Use either `default` alone, or `staging` and `production` together. Other
  environment names are not supported.
* All referenced paths must be clean paths inside the repository. They cannot
  be absolute paths or use `..`.
* `secrets_file` must point to a tracked SOPS-encrypted file.

See the [configuration reference](/config/) for all manifest fields and
`.dockiy.template.yml` options.

## Build the image locally

The build hook runs from the repository root on the developer's computer. It
must build and tag every image listed in `dockiy.yml`; DockIY pushes those tags
to the server registry after the hook succeeds.

For one image, `scripts/build.sh` can be:

```bash
#!/usr/bin/env bash
set -euo pipefail

docker build \
  --platform "$DOCKIY_PLATFORM" \
  --tag "$DOCKIY_APP_IMAGE" \
  .
```

For multiple images, build each configured image with its matching variable:

```bash
docker build --platform "$DOCKIY_PLATFORM" --tag "$DOCKIY_APP_IMAGE" .
docker build --platform "$DOCKIY_PLATFORM" --target migrate \
  --tag "$DOCKIY_MIGRATE_IMAGE" .
```

The hook must exit non-zero when a build fails. Do not push images from the
hook; the CLI manages the registry tunnel and pushes all configured images.

## Create a compatible Compose file

DockIY uploads the selected Compose file to the release directory as
`docker-compose.yml`, together with `deploy.env`. Use the generated image and
runtime variables instead of hardcoded release or registry values:

```yaml
name: ${DOCKIY_COMPOSE_PROJECT_NAME:?DOCKIY_COMPOSE_PROJECT_NAME is required}

services:
  app:
    image: ${DOCKIY_APP_IMAGE:?DOCKIY_APP_IMAGE is required}
    environment:
      APP_ENV: ${DOCKIY_ENVIRONMENT:?DOCKIY_ENVIRONMENT is required}
    networks:
      - reverse_proxy
    healthcheck:
      test: ["CMD", "wget", "--spider", "--quiet", "http://127.0.0.1:8080/"]
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=reverse_proxy"
      - "traefik.http.routers.${DOCKIY_COMPOSE_PROJECT_NAME}.rule=Host(`${DOCKIY_APP_HOST}`)"
      - "traefik.http.routers.${DOCKIY_COMPOSE_PROJECT_NAME}.entrypoints=websecure"
      - "traefik.http.routers.${DOCKIY_COMPOSE_PROJECT_NAME}.tls=true"
      - "traefik.http.routers.${DOCKIY_COMPOSE_PROJECT_NAME}.service=${DOCKIY_COMPOSE_PROJECT_NAME}"
      - "traefik.http.services.${DOCKIY_COMPOSE_PROJECT_NAME}.loadbalancer.server.port=8080"

networks:
  reverse_proxy:
    name: reverse_proxy
    external: true
```

The service must listen on the port named in the Traefik service label and join
the external `reverse_proxy` network. Do not publish a host port for the public
application; Traefik provides the HTTPS entrypoint. Add a container
`healthcheck` when the start hook uses `docker compose --wait`.

Values from a SOPS file are available to Compose through `deploy.env`, but
Compose variables are not automatically passed into a container. Map every
runtime value explicitly under `environment` or `secrets`.

## Write the deployment hooks

Hooks are executable files with a shebang. Use `set -euo pipefail` where the
script supports it, and make remote hooks safe to run again after a lost SSH
connection.

### Start hook

`start` is required and runs remotely after the release files are installed.
It should start the application and wait for the services to become ready:

```bash
#!/usr/bin/env bash
set -euo pipefail

docker compose --env-file deploy.env up \
  --detach \
  --remove-orphans \
  --pull always \
  --wait \
  app
```

The hook runs in a release directory containing `docker-compose.yml`,
`deploy.env`, and the uploaded hook files.

### Optional hooks

* `pre_start` runs before `start` during a normal deployment. Use it for
  transactional database migrations or other prerequisites.
* `rollback_pre_start` runs before `start` during a rollback. The normal
  `pre_start` hook does not run during rollback.
* `healthcheck` runs after `start` and must exit non-zero when the deployment
  is not usable.

For example, a migration pre-start hook can wait for the database and run a
one-shot migration service:

```bash
#!/usr/bin/env bash
set -euo pipefail

docker compose --env-file deploy.env up --detach --wait db
docker compose --env-file deploy.env run --rm migrate
```

Use a versioned and retry-safe migration tool. DockIY does not provide
exactly-once execution for hooks and does not roll back database changes.

## Use DockIY variables

The build hook receives these values locally:

| Variable | Meaning |
| --- | --- |
| `DOCKIY_PLATFORM` | Target Docker platform from the selected server configuration |
| `DOCKIY_<NAME>_IMAGE` | Local registry reference for each image in `images` |

Image names are uppercased and hyphens become underscores, so an image named
`worker-api` uses `DOCKIY_WORKER_API_IMAGE`.

The generated `deploy.env` contains these values for the selected environment:

| Variable | Meaning |
| --- | --- |
| `DOCKIY_APP_HOST` | Hostname from the selected environment |
| `DOCKIY_COMPOSE_PROJECT_NAME` | Compose project name: `name` for `default`, or `name-environment` |
| `DOCKIY_ENVIRONMENT` | `default`, `staging`, or `production` |
| `DOCKIY_GIT_COMMIT` | Git commit being deployed |
| `DOCKIY_RELEASE` | Release Git tag |
| `DOCKIY_<NAME>_IMAGE` | Server registry reference for each configured image |

Remote hooks receive `DOCKIY_COMPOSE_PROJECT_NAME` directly. The other
deployment values are in `deploy.env`; use `docker compose --env-file
deploy.env` when a hook needs them. Secrets from `secrets_file` are included in
the same file. Never add `DOCKIY_*` values or plaintext secrets to the tracked
encrypted files; DockIY supplies its own variables after decrypting them.

## Optional encrypted configuration

If the template needs secrets, commit encrypted environment files and point to
them from each environment's `secrets_file`:

```yaml
environments:
  production:
    host: app.example.com
    compose_file: docker-compose.yml
    secrets_file: .enc.production.env
```

The CLI decrypts the file on the developer's computer and uploads it as
`deploy.env`. Private identity keys remain on the developer's computer.

For reusable templates, add `.dockiy.template.yml` to remove maintainer-owned
ciphertext and create a fresh SOPS configuration during `dockiy app init`:

```yaml
remove:
  - .enc.production.env
sops:
  config_file: .sops.yaml
  path_regex: '^\.enc\..*\.env$'
```

## Test and deploy

Before publishing a template:

1. Build the image through the real build hook and verify that every configured
   image is created.
2. Run the Compose stack locally with a local `.env` file.
3. Check that the public service has a healthcheck, joins `reverse_proxy`, and
   uses the correct internal port in its Traefik label.
4. Run `dockiy config validate`, commit the repository, and deploy a test
   environment.

```bash
dockiy app deploy --version v0.1.0
# or, for a staging/production manifest:
dockiy app deploy staging
dockiy app deploy production --version v0.1.0
```

Use [Deploying applications](/guide/deploying-apps) for status, retries,
versioning, and rollback behavior.
