Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ FROM nginx:alpine
RUN rm -rf /usr/share/nginx/html/*
COPY . /usr/share/nginx/html/
COPY default.conf /etc/nginx/conf.d/default.conf
COPY docker/entrypoint/10-generate-config.sh /docker-entrypoint.d/10-generate-config.sh
RUN chmod +x /docker-entrypoint.d/10-generate-config.sh
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
55 changes: 46 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ Hestia comes with a suite of built-in apps. You can add as many as you like\!

## 🚀 Getting Started

### Option 1: Docker (Recommended)
### Option 1: Docker Compose (Recommended)

This is the best way to run Hestia if you plan to use the **API integrations** (Glances, Pi-hole, etc.), as the included Nginx config handles CORS permissions for you.
This is the fastest way to get Hestia (and its homelab proxies) online. The Compose file wires up Nginx, health checks, and optional proxy snippets for you.

1. Clone the repository:

Expand All @@ -65,16 +65,40 @@ This is the best way to run Hestia if you plan to use the **API integrations** (
cd hestia-core
```

2. Build and Run:
2. Copy the default environment file and tweak anything you need (host port, proxy targets, etc.):

```bash
docker build -t hestia-core .
docker run -d -p 8080:80 --name hestia hestia-core
cp compose.env.example .env
# edit .env to point at your Pi-hole / Deluge / Jellyfin hosts
```

3. Visit `http://localhost:8080`\!
> The upstream proxy blocks are **disabled by default** so the dashboard can boot without external services. Flip `ENABLE_*_PROXY` to `true` and set the `*_TARGET` values when you're ready to wire them up.

### Option 2: Static / Manual
3. (Optional) Drop any extra Nginx snippets into `config/nginx/` if you need to proxy additional apps.

4. Build and start the stack:

```bash
docker compose up -d --build
# Use --build only the first time or when updating
```

> Need Hestia to reach services defined in another Compose project? Add its network name under `services.hestia.networks` in `compose.yaml`, or connect the container later with `docker network connect`.

5. Visit `http://localhost:8080` (or whatever `HOST_PORT` you set).

### Option 2: Docker CLI

Prefer raw `docker build`/`docker run`? The classic workflow still works and now ships with the same dynamic config generator used by Compose.

```bash
docker build -t hestia-core .
docker run -d -p 8080:80 --name hestia --env-file .env hestia-core
```

Set any of the variables shown in `compose.env.example` (e.g., `PIHOLE_PROXY_TARGET`) to customize the generated Nginx config before starting the container.

### Option 3: Static / Manual

Since Hestia is vanilla JavaScript, you can run it on any web server.

Expand All @@ -91,13 +115,26 @@ python3 -m http.server 8000

### Using the Proxy (Docker Only)

To make the integrations work smoothly, Hestia's `default.conf` sets up internal proxies. When configuring apps in the dashboard, use these relative paths:
Hestia's container publishes a small reverse proxy for the integrations, so the UI can talk to your homelab services without CORS issues. When configuring apps inside the dashboard, point them at these relative paths:

* **Pi-hole URL:** `/pi-api/admin/api.php` (instead of `http://192.168.x.x/...`)
* **Deluge URL:** `/deluge-api/json`
* **Jellyfin URL:** `/jellyfin-api/`

*Note: You will need to update `default.conf` to point to your actual server IPs before building the Docker image\!*
All of the proxy blocks are generated from environment variables at container start-up. The most common knobs live in `.env`:

| Variable | Purpose |
| --- | --- |
| `ENABLE_PIHOLE_PROXY` | Toggle the Pi-hole proxy block without editing Nginx (default `false`). |
| `PIHOLE_PROXY_TARGET` | Upstream URL for your Pi-hole instance (required when `ENABLE_PIHOLE_PROXY=true`). |
| `ENABLE_DELUGE_PROXY` | Toggle the Deluge proxy block (default `false`). |
| `DELUGE_PROXY_TARGET` | RPC endpoint for Deluge (default `http://deluge:8112/`). |
| `ENABLE_JELLYFIN_PROXY` | Toggle the Jellyfin proxy block (default `false`). |
| `JELLYFIN_PROXY_TARGET` | Base URL for Jellyfin (default `http://jellyfin:8096/`). |

> Leave `PIHOLE_HOST_HEADER`, `DELUGE_HOST_HEADER`, and `JELLYFIN_HOST_HEADER` blank unless you need to override them—the entrypoint falls back to Nginx's `$host` variable automatically.

You can also change the exposed paths (`*_PROXY_PATH`) or disable any integration with `ENABLE_*_PROXY=false`. For additional services, drop custom `.conf` snippets into `config/nginx/`—they are loaded automatically on container start.

---

Expand Down
24 changes: 24 additions & 0 deletions compose.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copy this file to `.env` and adjust the values you need before running docker compose.
HOST_PORT=8080
NGINX_LISTEN_PORT=80

# Pi-hole proxy passthrough
ENABLE_PIHOLE_PROXY=false
PIHOLE_PROXY_PATH=/pi-api/
PIHOLE_PROXY_TARGET=
PIHOLE_HOST_HEADER=pi.hole # leave blank to fall back to Nginx's $host
PIHOLE_SSL_VERIFY=off

# Deluge proxy passthrough
ENABLE_DELUGE_PROXY=false
DELUGE_PROXY_PATH=/deluge-api/
DELUGE_PROXY_TARGET=http://deluge:8112/
# Leave empty to default to Nginx's $host variable
DELUGE_HOST_HEADER=

# Jellyfin proxy passthrough
ENABLE_JELLYFIN_PROXY=false
JELLYFIN_PROXY_PATH=/jellyfin-api/
JELLYFIN_PROXY_TARGET=http://jellyfin:8096/
# Leave empty to default to Nginx's $host variable
JELLYFIN_HOST_HEADER=
38 changes: 38 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
services:
hestia:
container_name: hestia-core
build:
context: .
dockerfile: Dockerfile
ports:
- "${HOST_PORT:-8080}:80"
environment:
NGINX_LISTEN_PORT: ${NGINX_LISTEN_PORT:-80}
ENABLE_PIHOLE_PROXY: ${ENABLE_PIHOLE_PROXY:-false}
PIHOLE_PROXY_PATH: ${PIHOLE_PROXY_PATH:-/pi-api/}
PIHOLE_PROXY_TARGET: ${PIHOLE_PROXY_TARGET:-}
PIHOLE_HOST_HEADER: ${PIHOLE_HOST_HEADER:-pi.hole}
PIHOLE_SSL_VERIFY: ${PIHOLE_SSL_VERIFY:-off}
ENABLE_DELUGE_PROXY: ${ENABLE_DELUGE_PROXY:-false}
DELUGE_PROXY_PATH: ${DELUGE_PROXY_PATH:-/deluge-api/}
DELUGE_PROXY_TARGET: ${DELUGE_PROXY_TARGET:-http://deluge:8112/}
DELUGE_HOST_HEADER: ${DELUGE_HOST_HEADER:-}
ENABLE_JELLYFIN_PROXY: ${ENABLE_JELLYFIN_PROXY:-false}
JELLYFIN_PROXY_PATH: ${JELLYFIN_PROXY_PATH:-/jellyfin-api/}
JELLYFIN_PROXY_TARGET: ${JELLYFIN_PROXY_TARGET:-http://jellyfin:8096/}
JELLYFIN_HOST_HEADER: ${JELLYFIN_HOST_HEADER:-}
volumes:
- ./config/nginx:/etc/nginx/server.d:ro
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:${NGINX_LISTEN_PORT:-80} > /dev/null"]
interval: 30s
timeout: 5s
retries: 5
start_period: 5s
networks:
- hestia

networks:
hestia:
driver: bridge
Empty file added config/nginx/.gitkeep
Empty file.
136 changes: 136 additions & 0 deletions docker/entrypoint/10-generate-config.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/bin/sh
set -e

CONF_DIR="/etc/nginx/conf.d"
CONF_FILE="${CONF_DIR}/default.conf"
SNIPPETS_DIR="/etc/nginx/server.d"

mkdir -p "${CONF_DIR}" "${SNIPPETS_DIR}"

normalize_path() {
value="$1"
if [ -z "${value}" ]; then
value="/"
fi
case "${value}" in
/*) : ;;
*) value="/${value}" ;;
esac
printf "%s" "${value}"
}

path_with_slash() {
value=$(normalize_path "$1")
case "${value}" in
*/) : ;;
*) value="${value}/" ;;
esac
printf "%s" "${value}"
}

path_no_slash() {
value=$(normalize_path "$1")
value="${value%/}"
if [ -z "${value}" ]; then
value="/"
fi
printf "%s" "${value}"
}

listen_port="${NGINX_LISTEN_PORT:-80}"
root_dir="${NGINX_ROOT:-/usr/share/nginx/html}"

cat > "${CONF_FILE}" <<EOF
server {
listen ${listen_port};
root ${root_dir};
index index.html;

error_log /dev/stdout info;
access_log /dev/stdout;

location / {
try_files \$uri \$uri/ =404;
}
EOF

append_proxy_block() {
block="$1"
cat >> "${CONF_FILE}" <<EOF
${block}
EOF
}

if [ "${ENABLE_DELUGE_PROXY:-true}" != "false" ]; then
deluge_path=$(path_with_slash "${DELUGE_PROXY_PATH:-/deluge-api/}")
deluge_regex=$(path_no_slash "${DELUGE_PROXY_PATH:-/deluge-api/}")
deluge_target="${DELUGE_PROXY_TARGET:-http://deluge:8112/}"
deluge_host_header="${DELUGE_HOST_HEADER:-}"
if [ -z "${deluge_host_header}" ]; then
deluge_host_header='\$host'
fi

append_proxy_block "
location ${deluge_path} {
proxy_pass ${deluge_target};

proxy_set_header Host ${deluge_host_header};
proxy_set_header X-Real-IP \$remote_addr;

proxy_cookie_path / ${deluge_path};
}
"
fi

if [ "${ENABLE_PIHOLE_PROXY:-true}" != "false" ]; then
pihole_path=$(path_with_slash "${PIHOLE_PROXY_PATH:-/pi-api/}")
pihole_regex=$(path_no_slash "${PIHOLE_PROXY_PATH:-/pi-api/}")
pihole_target="${PIHOLE_PROXY_TARGET:-}"
pihole_host_header="${PIHOLE_HOST_HEADER:-pi.hole}"
pihole_ssl_verify="${PIHOLE_SSL_VERIFY:-off}"
if [ -z "${pihole_host_header}" ]; then
pihole_host_header='\$host'
fi

if [ -z "${pihole_target}" ]; then
echo "[entrypoint] Pi-hole proxy enabled but PIHOLE_PROXY_TARGET is empty; skipping block."
else
append_proxy_block "
location ${pihole_path} {
rewrite ^${pihole_regex}/(.*) /\$1 break;

proxy_pass ${pihole_target};

proxy_ssl_verify ${pihole_ssl_verify};
proxy_ssl_server_name on;
proxy_set_header Host ${pihole_host_header};

proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
"
fi
fi

if [ "${ENABLE_JELLYFIN_PROXY:-true}" != "false" ]; then
jellyfin_path=$(path_with_slash "${JELLYFIN_PROXY_PATH:-/jellyfin-api/}")
jellyfin_target="${JELLYFIN_PROXY_TARGET:-http://jellyfin:8096/}"
jellyfin_host_header="${JELLYFIN_HOST_HEADER:-}"
if [ -z "${jellyfin_host_header}" ]; then
jellyfin_host_header='\$host'
fi

append_proxy_block "
location ${jellyfin_path} {
proxy_pass ${jellyfin_target};
proxy_set_header Host ${jellyfin_host_header};
proxy_set_header X-Real-IP \$remote_addr;
}
"
fi

cat >> "${CONF_FILE}" <<'EOF'

include /etc/nginx/server.d/*.conf;
}
EOF