Skip to content
Code on GitHub (opens in a new tab)

Documentation · Get started

Your first site

Install Ehundu, create a site with a menu and a blog, see it in the browser and publish it.

Version 0.16

In this walkthrough you install Ehundu and build a small site with a home page, another page, a menu and a blog. It is just enough to understand how a project is thought out. It will take you about twenty minutes.

One thing first: the format of an Ehundu project is written in Spanish on purpose, so folders and fields have Spanish names (contenido/, plantillas/, titulo, fecha…). Each one is explained the first time it appears, and the quick reference has all of them in one table.

Before you start

You need PHP 8.4 or later with the mbstring extension. Check your version:

php --version

If it says 8.4 or higher, and php -m includes mbstring, you can go on.

1. Install Ehundu

Ehundu is a single file, ehundu.phar. Download it from the latest release and put it wherever you like:

curl -L -o ehundu.phar https://github.com/ehundu/ehundu/releases/latest/download/ehundu.phar
php ehundu.phar --version

It should print Ehundu and the version number. On Windows, instead of curl you can use the browser or Invoke-WebRequest. If you prefer Composer, composer require ehundu/ehundu installs it and the command is vendor/bin/ehundu.

In what follows I write php ehundu.phar. If you installed it with Composer, change it to vendor/bin/ehundu.

2. Create the project

A project is a folder. Create one and go into it:

mkdir my-site
cd my-site

The only required file is sitio.yml (sitio is “site”), with the name of the site and the address of the domain where it will live, without a path. nombre is the name and url is the address:

nombre: My first site
url: https://example.com

3. Write a page and its template

Pages live in the contenido/ folder (“content”). They are written in Markdown, with some data at the top between two lines of dashes (it is called “front matter”). Create contenido/index.md; titulo is the title:

---
titulo: Hello, world
---
This is my first page with Ehundu.

Every page needs a template that wraps it in HTML. They are written in Twig, and the one used by default is plantillas/pagina.twig (“templates / page”). Create that file:

<!doctype html>
<html lang="{{ pagina.idioma }}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{{ pagina.titulo }} · {{ sitio.nombre }}</title>
</head>
<body>
  <h1>{{ pagina.titulo }}</h1>
  {{ pagina.contenido }}
</body>
</html>

pagina (“page”) is the page being built: its title is there, its text already converted to HTML (pagina.contenido) and any other data from the front matter. sitio (“site”) is what you wrote in sitio.yml.

4. See it

php ehundu.phar servir

Ehundu builds the site and tells you where to see it: http://127.0.0.1:8000/. Open it in the browser. Leave the command running: every time you save a change it rebuilds only what is needed, and reloading the page is enough. Ctrl+C stops it.

When you want the finished site, build it:

php ehundu.phar compilar

A salida/ folder (“output”) appears with plain, ordinary HTML, ready to upload to any server.

5. Another page and a menu

Every file in contenido/ is a page, and its address comes from its name. Create contenido/about-me.md, which will be /about-me/:

---
titulo: About me
---
My name is Ana and this is **my website**.

To make it reachable, a menu. Data that is repeated on several pages goes in the datos/ folder (“data”), one file each. Create datos/menu.yml:

- texto: Home
  enlace: /
- texto: About me
  enlace: /about-me/
- texto: Blog
  enlace: /blog/

And the pieces of HTML you reuse go in parciales/ (“partials”). Create parciales/menu.twig:

<nav>
  <ul>
    {% for entrada in datos.menu %}
    <li><a href="{{ entrada.enlace }}"{% if activo(entrada.enlace) %} aria-current="page"{% endif %}>{{ entrada.texto }}</a></li>
    {% endfor %}
  </ul>
</nav>

datos.menu is the content of datos/menu.yml, and activo() (“active”) tells you whether you are on that page. The partial is still missing from the templates. So as not to repeat the structure in each one, move what is common to plantillas/base.twig:

<!doctype html>
<html lang="{{ pagina.idioma }}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{{ pagina.titulo }} · {{ sitio.nombre }}</title>
</head>
<body>
  {% include 'parciales/menu.twig' %}
  <main>
    {% block cuerpo %}{% endblock %}
  </main>
</body>
</html>

And leave plantillas/pagina.twig with only what belongs to a page:

{% extends 'plantillas/base.twig' %}

{% block cuerpo %}
  <h1>{{ pagina.titulo }}</h1>
  {{ pagina.contenido }}
{% endblock %}

With the preview running, reload: both pages now have a menu.

6. A blog

Create a folder contenido/blog/ with a _datos.yml file. Everything you put there is inherited by the pages in the folder:

etiquetas: [blog]
url: "/blog/{{ titulo|slug }}/"

This way, every page in blog/ belongs to the blog collection (etiquetas means “tags”), and its address comes from its title (“My first post” becomes /blog/my-first-post/). Write two posts, each with a date. For example, contenido/blog/first-post.md:

---
titulo: My first post
fecha: 2026-10-15
---
Today I start a blog.

And contenido/blog/another-one.md, with another title and another date. Now the page that lists them, contenido/blog/index.md. It has its own address and uses another template, lista (“list”):

---
titulo: Blog
plantilla: lista
url: /blog/
---

And the template plantillas/lista.twig loops over the collection:

{% extends 'plantillas/base.twig' %}

{% block cuerpo %}
  <h1>{{ pagina.titulo }}</h1>
  <ul>
    {% for entrada in coleccion('blog')|sin(pagina)|orden('fecha desc') %}
    <li><a href="{{ entrada.url }}">{{ entrada.titulo }}</a>, {{ entrada.fecha|fecha }}</li>
    {% endfor %}
  </ul>
{% endblock %}

coleccion('blog') (“collection”) is the pages with that tag. sin(pagina) (“without”) removes the list page itself, which, being in the folder, carries the tag too. orden('fecha desc') (“order”) puts the most recent first, and fecha writes the day in the language of the page: “20 October 2026”.

7. Publish it

Tell Ehundu where to upload the site. Add the despliegue section (“deployment”) to sitio.yml (here, over SFTP):

despliegue:
  destino: sftp
  servidor: sftp.example.com
  usuario: ana
  ruta: /home/ana/www

The password never goes in sitio.yml, which is shared and versioned. It goes in a .secretos.yml file (“secrets”), at the root of the project, in single quotes:

despliegue:
  clave: 'the-password'

Before uploading anything, see what it would do:

php ehundu.phar desplegar --simular

The first time, with SFTP, it stops and gives you the server's fingerprint so that you can check it with whoever administers it and write it down in sitio.yml (huella:). When everything matches:

php ehundu.phar desplegar

It builds the site and uploads only what is new or has changed. If you use Git, do not version salida/, .ehundu/ or .secretos.yml. You can also publish to a folder, over FTP or to an S3 bucket: change the destino (“destination”).

And now what?