# Yacht: a self-hosted Docker UI built around Portainer-compatible templates

> Yacht is a Vue and Flask container management interface whose distinguishing feature is a template-driven one-click deployment model, plus an agent container for remote Docker hosts. Here is what the repository documents, where it is thin, and who should skip it.

**Yacht-sh/Yacht** — A web interface for managing docker containers with an emphasis on templating to provide 1 click deployments. Think of it like a decentralized app store for servers that anyone can make packages for.

- Repository: https://github.com/Yacht-sh/Yacht
- Stars: 3,873 · Forks: 174
- Language: Vue
- License: NOASSERTION
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/yacht-sh-yacht

## The problem Yacht solves: template JSON instead of repeated compose files

Most Docker UIs give you a container list and a log viewer. Yacht's stated focus is different: "templates and 1-click deployments", which the project describes as a decentralized app store for servers that anyone can make packages for. The practical consequence is that the unit of work is not a container but a template entry. You point Yacht at a template URL, it reads the file, splits it into apps, and imports those apps into its database. Each app row is linked to the template it came from by a database relationship, so deleting the template deletes the apps that came from it. Yacht stores the template URL itself, which is what makes the update button possible: pressing it re-reads the same URL rather than asking you to paste it again.

That design targets a specific person: someone running a home server or small fleet who installs the same handful of services repeatedly across machines and is tired of hand-editing compose files. It is a worse fit for someone who wants to inspect and diff every container definition, because the template layer sits between you and the compose file. Yacht is compatible with Portainer templates, so an existing template collection is not wasted, and the README recommends starting with the wickedyoda/selfhosted_templates repository's yacht branch. Templates can define variables beginning with ! that get replaced by values from your server settings; the README's example is !config, which resolves to /yacht/AppData/Config by default. That substitution is the mechanism that makes one template work across machines with different paths.

## How Yacht is put together: Vue frontend, Flask backend, nginx in one image

The Dockerfile is a three-stage build and it tells you more about the architecture than the README does. Stage one is node:20-bookworm-slim, which runs npm ci --legacy-peer-deps --no-audit --no-fund against frontend/package.json and then npm run build to produce /app/dist. Stage two is python:3.11-slim with a compiler toolchain; it installs build-essential, libffi-dev, libssl-dev, libpq-dev, default-libmysqlclient-dev and others, then builds wheels from backend/requirements.txt into /wheels. Stage three is another python:3.11-slim that installs only runtime libraries (nginx, libpq5, libmariadb3, libjpeg62-turbo), copies the wheels, installs them with --no-index --find-links=/wheels, copies backend/ to /api, nginx.conf to /etc/nginx/nginx.conf, docker/start.sh to /usr/local/bin/start.sh, and the built frontend to /app. The runtime image sets PYTHONPATH=/api and THEME=Default.

The split matters for two reasons. First, the toolchain never reaches the runtime image, so the shipped container is smaller than a naive single-stage build. Second, the presence of libpq5 and libmariadb3 in the runtime stage is consistent with the DATABASE_URL variable, which the README says lets you point Yacht at a database such as SQL instead of the built-in sqlite. The frontend and backend are served from one container behind nginx, so there is no separate API host to configure. The compose file in the repository maps port 9000 on the host to 8000 in the container, which is the port nginx listens on inside the image.

## Installing Yacht and deploying your first template

The README does not carry install steps; it says installation documentation lives at dev.yacht.sh, with a getting started guide at dev.yacht.sh/docs/Installation/Getting_Started, and that currently only Linux has been verified as working. The repository does ship a docker-compose.yaml at the top level, and that is the most concrete install artifact available. It builds from the local Dockerfile with VUE_APP_VERSION set to local-dev, names the container yacht-local, and publishes port 9000.

```yaml
services:
  yacht:
    container_name: yacht-local
    build:
      context: .
      dockerfile: Dockerfile
      args:
        VUE_APP_VERSION: local-dev
    image: yacht:local-dev
    restart: unless-stopped
    ports:
      - "9000:8000"
    environment:
      SECRET_KEY: local-dev-secret-key-change-me-please
      DISABLE_AUTH: "False"
      COMPOSE_DIR: /config/compose/
      THEME: Default
    volumes:
      - yacht-config:/config
      - /var/run/docker.sock:/var/run/docker.sock
```

Two things in that file deserve attention before you run it. The Docker socket is mounted into the container, which is how Yacht manages containers at all, and it grants the container effective control over the host's Docker daemon. The SECRET_KEY value is the literal string local-dev-secret-key-change-me-please; the README states that setting SECRET_KEY to a random string is what prevents you being logged out between reboots. Replace it. DISABLE_AUTH is set to "False" here, which matches the README's position that disabling authentication is not recommended unless you front Yacht with something like Authelia.

```bash
docker compose up -d
```

After the build finishes, the UI is on port 9000 of the host. The next step is the template URL, taken from the README's recommendation:

```
https://raw.githubusercontent.com/wickedyoda/selfhosted_templates/yacht/Template/template.json
```

You add that URL in the Add Template settings. According to the README, Yacht reads the file, separates it into apps, imports them into the database, and links each app to its template. From there the apps appear as one-click deployments, and the update button re-reads the stored URL. If your template uses !config style variables, they resolve against the values in your server settings, so set those before deploying rather than after.

## Agent-managed hosts, and what the agent cannot do yet

The develop branch supports two host modes. The first is direct: you add a Docker API host manually in the UI. The second is agent-managed, where a separate yacht-agent container mounts the local Docker socket, registers with the main Yacht server using a shared enrollment token, keeps host inventory in sync, and executes container actions locally after the server queues a job. The README frames the benefit narrowly and honestly: this avoids exposing the remote Docker API directly when you do not want to.

The agent deployment example in the README sets YACHT_SERVER_URL, YACHT_AGENT_ENROLLMENT_TOKEN and YACHT_AGENT_NAME, and mounts /var/run/docker.sock plus a config volume. The enrollment token on the agent must match AGENT_ENROLLMENT_TOKEN on the main server. Agent-managed hosts self-register, and the README is explicit that you do not create them manually from the Hosts form. Defaults worth knowing: YACHT_AGENT_STATE is /config/agent-state.json, YACHT_AGENT_HEARTBEAT_INTERVAL is 30 seconds, YACHT_AGENT_JOB_POLL_INTERVAL is 5 seconds, and YACHT_AGENT_LOG_LEVEL is INFO. YACHT_AGENT_VERIFY_SSL should only be set to false when the Yacht server uses a self-signed certificate the agent cannot validate, which is a real weakening of the connection and should be a temporary measure.

The limitation is spelled out in the README's own list. Agent-backed write support on develop currently covers container actions start, stop, restart, kill and remove. Compose actions are narrower still: if the enrolled agent reports compose capability, the UI exposes per-host up, down and pull, using the compose project name from the agent host, with the server queuing and the agent executing. Anything outside that set has no documented agent path. Note also that the README's agent section is written about the develop branch, while the repository's default branch is master, so the feature set you get depends on which branch you build.

## Where Yacht is the wrong tool

The README's own feature lists are the clearest limitation. Container Monitoring, Easy access to container CLI, User Management and Scheduled Jobs all sit under Planned Features, not under Features So Far. If your reason for wanting a Docker UI is per-container metrics, an in-browser shell, multiple user accounts with different permissions, or cron-style jobs, Yacht does not document any of them as shipped. The ARM note is a related constraint: on ARM devices, if graphs are not showing up, the README says to add cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 to cmdline.txt. That is a host-level boot parameter change, not a setting inside Yacht, and it implies the monitoring view depends on cgroup accounting the kernel is not exposing by default.

Platform support is the second boundary. The README states that currently only Linux has been verified as working and that Windows support is still being evaluated. Running the container on Windows via Docker Desktop is not the same claim, and the README does not make it. Third, the documentation is deliberately distributed: the README points to dev.yacht.sh for installation, to docs/ for repo-local documentation and to wiki/ for operational pages on the develop branch. Anyone expecting one authoritative page will be reading three sources. Finally, the repository's LICENSE and LICENSE.md exist at the top level, but the project's licence identifier is reported as NOASSERTION, meaning no standard SPDX identifier was detected. If licence terms decide whether you can use this at work, read those files directly rather than trusting a badge.

## How Yacht differs from Portainer

The obvious comparison is Portainer, and the difference is not the container list. Yacht is compatible with Portainer templates by design, so both tools can consume the same template JSON. The divergence is in what the template means to each product and what sits around it. Yacht's README describes the template as the primary path: you add a URL, the file is parsed into apps, the apps are stored with a relationship back to the template, and the URL is retained so a button press refreshes everything. The variable substitution scheme, where !config expands to a path from your server settings, is Yacht's own layer on top of that format and is what makes a shared template usable across machines with different layouts.

The second difference is the agent. Yacht's agent model has the remote host initiate the connection: the yacht-agent container mounts its local Docker socket, registers outbound with the main server using a shared enrollment token, and polls for queued jobs. That is a different trust shape from adding a Docker API endpoint in a UI, because the remote daemon is never exposed on the network. The cost is coverage. Yacht's agent handles five container actions and, when the agent reports compose capability, three compose lifecycle jobs. Anything beyond that list has no documented agent path, so a fleet that needs arbitrary Docker API calls through the agent will hit the edge of the design quickly.

## Maintenance status, upgrade cost and licence

The repository is not archived and the last push was on 2026-09-22, one day before this writing, so the project is being worked on. Release history is uneven, though, and the tags tell the story better than the commit activity does: v0.0.7-alpha landed on 2021-04-23, a hotfix v0.0.7-alpha-hf-1 followed on 2021-05-31, and the next release, 1.1, is dated 2026-09-02. A five-year gap between tagged releases means anyone tracking versions rather than commits has had little to pin to. It also means upgrade notes are sparse: the repository has CHANGELOG.md and changes.md, but the README does not document a rollback path, a database migration procedure, or what happens to imported apps and templates when you move between versions.

Upgrade cost concentrates in three places. The image is built from source in the repository compose file, so a local build pins whatever the Dockerfile's base images resolve to at build time rather than a digest you chose. The database is sqlite by default, with DATABASE_URL available if you want something else; switching engines after you have imported templates is not described in the README, so decide before you populate. The agent adds a second moving part with its own state file at /config/agent-state.json, and the README does not describe how to re-enroll an agent or what happens to its queued jobs if the main server is unavailable. On licensing, LICENSE and LICENSE.md are present in the repository but the detected identifier is NOASSERTION, so there is no SPDX string to rely on. Read the licence file before distributing a modified image or shipping Yacht inside a product.

## Conclusion

Adopt Yacht if you run Docker on Linux, you want a browser UI whose main job is turning template JSON into one-click stacks, and you are comfortable reading docs at dev.yacht.sh plus the repo's docs/ and wiki/ folders rather than the README. Do not adopt it if you need Windows support today (the README says Windows is still being evaluated), if you need scheduled jobs or user management (both sit in the planned list, not the shipped list), or if you expect the README to explain rollback, backup or licence terms. Before deploying, verify three things: that your template URL loads and imports, that SECRET_KEY is set to a random string so sessions survive a restart, and whether you need the agent path at all, since remote hosts must self-register with a matching AGENT_ENROLLMENT_TOKEN and YACHT_AGENT_ENROLLMENT_TOKEN and cannot be created from the Hosts form.

## FAQ

### How do I install Yacht?

The README points installation documentation to dev.yacht.sh, with a getting started guide at dev.yacht.sh/docs/Installation/Getting_Started, and notes that only Linux has been verified as working. The repository also ships a top-level docker-compose.yaml that builds from the local Dockerfile and publishes port 9000 on the host, mapped to port 8000 in the container.

### What template URL should I use with Yacht?

The README recommends starting with the wickedyoda/selfhosted_templates repository's yacht branch template.json. You add that URL in the Add Template settings, and Yacht reads the file, separates it into apps, imports them into the database and stores the URL so you can refresh the template with a button press.

### Does Yacht support remote Docker hosts?

Yes, in two modes on the develop branch: direct Docker API hosts added manually in the UI, and agent-managed hosts where a yacht-agent container connects back to the main Yacht server. Agent-managed hosts self-register using a shared enrollment token and are not created manually from the Hosts form.

### What can the Yacht agent actually do on a remote host?

The README lists agent-backed container actions as start, stop, restart, kill and remove. If the enrolled agent reports compose capability, the UI also exposes per-host up, down and pull actions, which the server queues and the agent executes against its local Docker environment.

### Why do I get logged out of Yacht after a restart?

The README states that setting SECRET_KEY to a random string ensures you will not be logged out between reboots of Yacht. The repository's own compose file ships a placeholder value, local-dev-secret-key-change-me-please, which is meant to be replaced.

## Sources

- [Issues](https://github.com/Yacht-sh/Yacht/issues)
- [README](https://github.com/Yacht-sh/Yacht/blob/master/README.md)
- [Releases](https://github.com/Yacht-sh/Yacht/releases)
- [Yacht-sh/Yacht on GitHub](https://github.com/Yacht-sh/Yacht)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/yacht-sh-yacht
