> For the complete documentation index, see [llms.txt](https://docs.onelitefeather.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.onelitefeather.net/ingot/install-and-run/serve-custom-frontend.md).

# Serve a custom frontend

Serve your own HTML, CSS, JavaScript and images from an Ingot server, use frontend placeholders, and choose whether the bundled dashboard stays on.

Ingot can serve your own static files next to its API. Use this for a landing page, a status page or a replacement dashboard. The bundled dashboard can stay on, or you can turn it off so that your files answer the root path.

## Prerequisites

* Shell access to the host, or to the container that runs Ingot.
* Basic knowledge of HTML.
* Read the warning in step 6 first. Everything in the directory is public.

## Steps

1. Find the directory. It is named `static` and sits in the working directory. In the container image the working directory is `/app/data`, so the directory is `/app/data/static`. Ingot creates it on the first start if it does not exist, and fills it with a default `index.html`. It also adds a `favicon.png` when there is none.

   If you create the directory yourself, add an `index.html` too. Ingot only writes its default `index.html` when it creates the directory.
2. Put your files in the directory. Ingot maps them to URLs as follows:

   | File                  | URL                                        |
   | --------------------- | ------------------------------------------ |
   | `static/index.html`   | `/`                                        |
   | `static/status.html`  | `/status.html`                             |
   | `static/app.js`       | `/app.js`                                  |
   | `static/img/logo.png` | `/img/<path>`, for example `/img/logo.png` |

   Files directly in the directory are served at `/<file>`. Files in a subdirectory are served at `/<directory>/<path>`.
3. Use placeholders if you want the server to fill in values. Ingot replaces placeholders such as `{{REPOSILITE.TITLE}}` when it serves a file. Placeholders are only substituted in:

   * top-level files whose name contains `.html` or `.js`. This includes `index.html`, and it also matches names such as `data.json`.

   Files in subdirectories are served unchanged. The placeholder names and values are listed in the [Plugin API reference](/ingot/http-and-web/plugin-api.md#frontend-placeholders).

   ```html
   <!doctype html>
   <html>
     <head>
       <meta charset="utf-8">
       <title>{{REPOSILITE.TITLE}}</title>
     </head>
     <body>
       <h1>{{REPOSILITE.TITLE}}</h1>
       <p>{{REPOSILITE.DESCRIPTION}}</p>
     </body>
   </html>
   ```
4. Choose how the bundled dashboard behaves.

   * Default: `INGOT_LOCAL_DEFAULTFRONTEND=true`. The bundled dashboard owns `/`, `/index.html` and `/assets/<path>`. These routes are registered before your files, so your `index.html` is not served at `/`. Your other top-level files are still served.
   * Off: `INGOT_LOCAL_DEFAULTFRONTEND=false`. The bundled dashboard routes are not registered, and your `index.html` answers `/`.

   In the compose file from the repository, add the variable under `environment`:

   ```yaml
   services:
     ingot:
       environment:
         - INGOT_LOCAL_DEFAULTFRONTEND=false
   ```

   The repository also ships `docker-compose.split.yml`, which runs the dashboard in its own container.
5. Restart Ingot after you change the files. The server builds its routes at startup, so a new top-level file or a new subdirectory is served only after a restart. Ingot also keeps a processed copy of each such file until the restart or until a frontend setting changes. Edits to other files, and new files inside an existing subdirectory, are read on the next request.

   ```bash
   docker compose restart ingot
   ```
6. Check the result.

   ```bash
   curl -i http://localhost:8080/
   curl -s http://localhost:8080/ | grep "<title>"
   ```

   The `<title>` line shows the title from the frontend settings, not the placeholder text.

{% hint style="warning" %}
Anyone who can reach the server can read every file in the directory. Ingot serves these files without an access check. Do not put secrets, configuration files or private data in it.
{% endhint %}

{% hint style="warning" %}
With the bundled dashboard on, a subdirectory named `assets` is shadowed by the dashboard route `/assets/<path>`. Name your directories differently, or turn the bundled dashboard off.
{% endhint %}

## Result

Your `index.html` answers the root path when the bundled dashboard is off. When it is on, the dashboard answers the root path, and your files answer their own paths. The placeholders in your top-level HTML and JavaScript files show the configured values.

## Related

* [Plugin API, frontend placeholders](/ingot/http-and-web/plugin-api.md#frontend-placeholders): the placeholder list, and how a plugin adds its own.
* [Write a plugin](/ingot/repositories-and-artifacts/write-a-plugin.md): a plugin can register a placeholder for your files to use.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.onelitefeather.net/ingot/install-and-run/serve-custom-frontend.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
