non-root enviroment and smaller bugfixes

This commit is contained in:
ChrispyBacon-dev 2025-09-22 16:41:09 +02:00
parent 1822b74a52
commit d2a0101aff
10 changed files with 222 additions and 69 deletions

1
.gitignore vendored
View file

@ -54,6 +54,7 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
AGENTS.md
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json

View file

@ -83,6 +83,34 @@ Before you begin, ensure you have the following:
```yaml
version: '3.8'
services:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy:v0.4.1
container_name: docker-socket-proxy
restart: unless-stopped
environment:
- DOCKER_HOST=unix:///var/run/docker.sock
- CONTAINERS=1
- EVENTS=1
- NETWORKS=1
- IMAGES=1
- POST=1
- PING=1
- INFO=1
- EXEC=1
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- dockflare-internal
dockflare-init:
image: alpine:3.20
command: ["sh", "-c", "chown -R 65532:65532 /app/data"]
volumes:
- dockflare_data:/app/data
networks:
- dockflare-internal
restart: "no"
dockflare:
image: alplat/dockflare:stable
container_name: dockflare
@ -90,13 +118,17 @@ services:
ports:
- "5000:5000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
# Persist encrypted configuration, state, and audit logs
- ./dockflare_data:/app/data
- dockflare_data:/app/data
environment:
- REDIS_URL=redis://redis:6379/0
- DOCKER_HOST=tcp://docker-socket-proxy:2375
depends_on:
- redis
docker-socket-proxy:
condition: service_started
dockflare-init:
condition: service_completed_successfully
redis:
condition: service_started
networks:
- cloudflare-net
- dockflare-internal
@ -107,7 +139,7 @@ services:
restart: unless-stopped
command: ["redis-server", "--save", "", "--appendonly", "no"]
volumes:
- ./dockflare_redis:/data
- dockflare_redis:/data
networks:
- dockflare-internal
@ -132,9 +164,9 @@ networks:
4. **For Existing Users**: If you are upgrading, DockFlare will detect your old `.env` file and automatically guide you through a quick migration process.
Redis runs on a private `dockflare-internal` network so only the DockFlare container can access it.
The master now runs as the unprivileged `dockflare` user (UID/GID 65532) and only talks to Docker through the bundled socket proxy. If you bind-mount a host directory instead of using the named volume above, make sure it is writable by that UID/GID or adjust the `DOCKFLARE_UID`/`DOCKFLARE_GID` build args.
💡 *Need to manage workloads on additional hosts?* Deploy the [DockFlare Agent](https://github.com/ChrispyBacon-dev/DockFlare-Agent-prd) next to your containers, enrol it from the master UI, and let DockFlare orchestrate the tunnels for you. The agent image now runs as the non-root `dockflare` user (UID/GID 65532), so ensure any mounted directories are writable by that account or adjust the build-time `DOCKFLARE_UID/DOCKFLARE_GID` args. A full guide is available in the new [Multi-Server Agent](dockflare/app/templates/docs/Multi-Server-Agent.md) documentation.
💡 *Need to manage workloads on additional hosts?* Deploy the [DockFlare Agent](https://github.com/ChrispyBacon-dev/DockFlare-Agent-prd) next to your containers, enrol it from the master UI, and let DockFlare orchestrate the tunnels for you. Both the master and agent images run as the non-root `dockflare` user by default, so align your volume permissions or override the build args if required. A full guide is available in the new [Multi-Server Agent](dockflare/app/templates/docs/Multi-Server-Agent.md) documentation.
</details>

View file

@ -1,31 +1,69 @@
version: '3.8'
services:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy:v0.4.1
container_name: docker-socket-proxy
restart: unless-stopped
environment:
- DOCKER_HOST=unix:///var/run/docker.sock
- CONTAINERS=1
- EVENTS=1
- NETWORKS=1
- IMAGES=1
- POST=1
- PING=1
- INFO=1
- EXEC=1
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- dockflare-internal
dockflare-init:
image: alpine:3.20
container_name: dockflare-init
command: ["sh", "-c", "chown -R ${DOCKFLARE_UID:-65532}:${DOCKFLARE_GID:-65532} /app/data"]
volumes:
- dockflare_data:/app/data
networks:
- dockflare-internal
restart: "no"
dockflare:
#image: alplat/dockflare:stable # alplat/dockflare:unstable docker tag for beta versions
build: ./dockflare # Uncomment to build from source instead
build:
context: ./dockflare
args:
DOCKFLARE_UID: ${DOCKFLARE_UID:-65532}
DOCKFLARE_GID: ${DOCKFLARE_GID:-65532}
container_name: dockflare
restart: unless-stopped
ports:
- "5001:5000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro # Required to monitor Docker events
- dockflare_data:/app/data # Persistent storage for state
- dockflare_data:/app/data
networks:
- cloudflare-net # Network for communication with other containers
- cloudflare-net
- dockflare-internal
depends_on:
- redis
docker-socket-proxy:
condition: service_started
dockflare-init:
condition: service_completed_successfully
redis:
condition: service_started
environment:
- STATE_FILE_PATH=/app/data/state.json
- TZ=Europe/Zurich # Set your timezone here
- TZ=Europe/Zurich
- REDIS_URL=redis://redis:6379/0
- CACHE_ENABLED=true
- DNS_RECORDS_CACHE_TIMEOUT=300
labels: # Optional
- dockflare.enable=true
- dockflare.hostname=master.dockflare.app
- dockflare.service=http://host.docker.internal:5001
- dockflare.access.policy=bypass
- DOCKER_HOST=tcp://docker-socket-proxy:2375
labels:
- dockflare.enable=true
- dockflare.hostname=master.dockflare.app
- dockflare.service=http://host.docker.internal:5001
- dockflare.access.policy=bypass
redis:
image: redis:7-alpine
container_name: dockflare-redis
@ -43,9 +81,10 @@ services:
volumes:
dockflare_data:
redis-data:
networks:
cloudflare-net:
name: cloudflare-net
external: true
name: cloudflare-net
external: true
dockflare-internal:
name: dockflare-internal

View file

@ -32,19 +32,24 @@ RUN echo "DEBUG: Details of /usr/src/app/app/static AFTER npm run build:css:" &&
RUN echo "DEBUG: Contents of /usr/src/app/app/static AFTER npm run build:css:" && ls -Alp /usr/src/app/app/static || echo "/usr/src/app/app/static NOT FOUND after build"
RUN echo "DEBUG: Specifically checking for /usr/src/app/app/static/css/output.css AFTER npm run build:css:"
RUN ls -l /usr/src/app/app/static/css/output.css || echo "/usr/src/app/app/static/css/output.css NOT FOUND after build"
FROM python:3.13-slim AS runtime
ARG DOCKFLARE_UID=65532
ARG DOCKFLARE_GID=65532
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
wget \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends wget ca-certificates && rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN mkdir -p /app/static/css
COPY --from=frontend-builder /usr/src/app/app/static/css/output.css /app/static/css/output.css
COPY ./app /app
RUN addgroup --system --gid ${DOCKFLARE_GID} dockflare \
&& adduser --system --uid ${DOCKFLARE_UID} --ingroup dockflare dockflare \
&& mkdir -p /app/data \
&& chown -R dockflare:dockflare /app
USER dockflare:dockflare
ENV PYTHONPATH=/
EXPOSE 5000
CMD ["python", "main.py"]

View file

@ -21,6 +21,34 @@ Modify your `docker-compose.yml` file to include the `nginx` service. The key is
version: '3.8'
services:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy:v0.4.1
container_name: docker-socket-proxy
restart: unless-stopped
environment:
- DOCKER_HOST=unix:///var/run/docker.sock
- CONTAINERS=1
- EVENTS=1
- NETWORKS=1
- IMAGES=1
- POST=1
- PING=1
- INFO=1
- EXEC=1
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- dockflare-internal
dockflare-init:
image: alpine:3.20
command: ["sh", "-c", "chown -R 65532:65532 /app/data"]
volumes:
- dockflare_data:/app/data
networks:
- dockflare-internal
restart: "no"
dockflare:
image: alplat/dockflare:stable
container_name: dockflare
@ -28,12 +56,17 @@ services:
ports:
- "5000:5000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./dockflare_data:/app/data
- dockflare_data:/app/data
environment:
- REDIS_URL=redis://redis:6379/0
- DOCKER_HOST=tcp://docker-socket-proxy:2375
depends_on:
- redis
docker-socket-proxy:
condition: service_started
dockflare-init:
condition: service_completed_successfully
redis:
condition: service_started
networks:
- cloudflare-net
- dockflare-internal
@ -44,9 +77,8 @@ services:
container_name: my-nginx
restart: unless-stopped
networks:
- cloudflare-net # Must be on the same network as DockFlare
- cloudflare-net
labels:
# --- DockFlare Configuration ---
- "dockflare.enable=true"
- "dockflare.hostname=nginx.example.com"
- "dockflare.service=http://nginx-webserver:80"
@ -57,7 +89,7 @@ services:
restart: unless-stopped
command: ["redis-server", "--save", "", "--appendonly", "no"]
volumes:
- ./dockflare_redis:/data
- dockflare_redis:/data
networks:
- dockflare-internal
@ -72,7 +104,7 @@ networks:
dockflare-internal:
name: dockflare-internal
```
> **Why Redis?** DockFlare relies on Redis for caching, log streaming, and cross-thread messaging. Running it on a private `dockflare-internal` network keeps Redis reachable only by DockFlare while workloads stay on `cloudflare-net`.
> **Why Redis?** DockFlare relies on Redis for caching, log streaming, and cross-thread messaging. Running it on the private `dockflare-internal` network keeps Redis reachable only by DockFlare, while workloads stay isolated on `cloudflare-net`.
### 2. Understanding the Labels

View file

@ -14,7 +14,7 @@ This page lists some of the common issues that users may encounter and how to re
2. **Look for Errors:** Look for any error messages. Common causes include:
* An invalid `docker-compose.yml` file (e.g., incorrect syntax, volume mounting issues).
* Problems with the Docker daemon itself.
* Permission issues with the Docker socket (`/var/run/docker.sock`).
* Connectivity or permission issues with the docker-socket-proxy service or the `DOCKER_HOST` setting.
---

View file

@ -1,38 +1,63 @@
# Quick Start (Docker Compose)
This guide will walk you through the fastest way to get DockFlare up and running using Docker Compose.
This guide walks through the fastest way to run DockFlare with the hardened socket proxy and rootless master configuration.
### 1. Create the `docker-compose.yml` file
First, create a `docker-compose.yml` file with the following content. This configuration uses the stable image of DockFlare, maps the required Docker socket, provisions Redis for caching/queueing, and sets up persistent volumes for both DockFlare and Redis.
The stack below launches the docker-socket-proxy, primes the persistent volume with the correct ownership, and starts DockFlare alongside Redis.
```yaml
version: '3.8'
services:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy:v0.4.1
container_name: docker-socket-proxy
restart: unless-stopped
environment:
- DOCKER_HOST=unix:///var/run/docker.sock
- CONTAINERS=1
- EVENTS=1
- NETWORKS=1
- IMAGES=1
- POST=1
- PING=1
- INFO=1
- EXEC=1
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- dockflare-internal
dockflare-init:
image: alpine:3.20
command: ["sh", "-c", "chown -R 65532:65532 /app/data"]
volumes:
- dockflare_data:/app/data
networks:
- dockflare-internal
restart: "no"
dockflare:
image: alplat/dockflare:stable
container_name: dockflare
restart: unless-stopped
ports:
- "5000:5000" # Exposes the web UI on the default port
- "5000:5000"
volumes:
# Mount the Docker socket (read-only)
- /var/run/docker.sock:/var/run/docker.sock:ro
# This volume is crucial for persisting your encrypted configuration
- ./dockflare_data:/app/data
- dockflare_data:/app/data
environment:
- REDIS_URL=redis://redis:6379/0
- DOCKER_HOST=tcp://docker-socket-proxy:2375
depends_on:
- redis
docker-socket-proxy:
condition: service_started
dockflare-init:
condition: service_completed_successfully
redis:
condition: service_started
networks:
- cloudflare-net
- dockflare-internal
# Optional: publish the DockFlare UI itself via Cloudflare
# labels:
# - "dockflare.enable=true"
# - "dockflare.hostname=dockflare.domain.tld"
# - "dockflare.service=http://dockflare:5000"
redis:
image: redis:7-alpine
@ -40,16 +65,14 @@ services:
restart: unless-stopped
command: ["redis-server", "--save", "", "--appendonly", "no"]
volumes:
- ./dockflare_redis:/data
- dockflare_redis:/data
networks:
- dockflare-internal
# This volume stores your encrypted credentials and state
volumes:
dockflare_data:
dockflare_redis:
# It is recommended to use an external network for your services
networks:
cloudflare-net:
name: cloudflare-net
@ -59,29 +82,31 @@ networks:
```
**Notes:**
- Before running the compose file, ensure the external network `cloudflare-net` exists: `docker network create cloudflare-net`.
- The private `dockflare-internal` network isolates Redis so only the DockFlare container can access it, even if other services join `cloudflare-net`.
- The master container runs as the `dockflare` user (UID/GID 65532). If you need to match different host permissions, set `DOCKFLARE_UID`/`DOCKFLARE_GID` and rebuild the image or adjust the init job.
- The proxy is mandatory. DockFlare never mounts `/var/run/docker.sock` directly, which limits the Docker API surface the master can reach.
- When using bind mounts instead of named volumes, make sure the target directory is writable by UID/GID 65532 (or your overridden values).
- Create the external network once if it does not exist: `docker network create cloudflare-net`.
### 2. Run DockFlare
Once you have saved the `docker-compose.yml` file, you can start DockFlare with the following command:
Start the stack in detached mode:
```bash
docker compose up -d
```
This will pull the latest stable image and start the DockFlare container in the background.
This brings up the proxy, primes the volume, and launches DockFlare together with Redis.
### 3. Complete the Pre-Flight Setup
After starting the container, open your web browser and navigate to `http://<your-server-ip>:5000`.
After the services are running, open your browser to `http://<your-server-ip>:5000`.
You will be greeted by the **Pre-Flight Setup Wizard**. This one-time process will guide you through:
1. Creating a password for the Web UI.
2. Entering your Cloudflare credentials (Account ID, Zone ID, and API Token).
3. Configuring your initial Cloudflare Tunnel.
4. *(Optional)* Restoring from a full DockFlare backup archive. If you already have a `dockflare_backup_*.zip`, choose **Restore from backup** before Step1—the wizard will import your configuration and automatically restart the container.
The **Pre-Flight Setup Wizard** walks you through:
1. Creating a password for the Web UI.
2. Entering your Cloudflare credentials (Account ID, Zone ID, API Token).
3. Configuring your initial Cloudflare Tunnel.
4. *(Optional)* Restoring from a DockFlare backup archive. If you already have a `dockflare_backup_*.zip`, choose **Restore from backup** before Step1; the wizard imports your configuration and restarts the container automatically.
### 4. For Existing Users (Upgrading)
If you are upgrading from an older version of DockFlare that used a `.env` file, DockFlare will automatically detect it. You will be guided through a simple migration process to import your existing settings and create a password for the new secure setup.
If you are upgrading from an older release, DockFlare detects the legacy `.env` file, migrates your configuration into the encrypted store, and guides you through password creation. Keep the socket proxy in place—direct mounts of `/var/run/docker.sock` are no longer supported.

View file

@ -26,7 +26,7 @@ This document explains how DockFlare secures both the Master node and enrolled A
- **Cloudflare Tunnel Transport** Agents expose no inbound ports. All traffic traverses the Cloudflare tunnel managed by the Master, reducing the attack surface on remote hosts.
- **Authenticated Agent Calls** Agent REST calls include their API key and are bound to their recorded agent ID. Token mismatches or revoked keys are rejected.
- **Redis Backplane** DockFlare relies on Redis for caching, log streaming, and cross-thread signalling. The recommended compose stack keeps Redis on a dedicated `dockflare-internal` network so workloads on `cloudflare-net` cannot reach it directly. Secure external Redis services with auth/TLS if you use them.
- **Least-privilege runtime** Agents run as the `dockflare` user (UID/GID 65532) inside the container and are designed to reach Docker through the bundled socket proxy so only inspection and lifecycle endpoints are exposed.
- **Least-privilege runtime** Both the master and agents run as the `dockflare` user (UID/GID 65532) and talk to Docker exclusively through the bundled socket proxy, keeping the exposed API surface minimal.
## 5. Authentication & Authorization
@ -44,7 +44,7 @@ This document explains how DockFlare secures both the Master node and enrolled A
| Area | Recommendation |
| --- | --- |
| Docker Volumes | Persist `/app/data` (encrypted config, keys, state). Persist `/app/logs` if file logging is enabled. |
| Docker Volumes | Persist `/app/data` (encrypted config, keys, state). Persist `/app/logs` if file logging is enabled, and ensure host mounts are writable by UID/GID 65532 or your overridden build args. |
| Redis | Run `redis:7-alpine` alongside DockFlare on a private network (`dockflare-internal`) or point `REDIS_URL` to a hardened instance (auth/TLS). Avoid exposing Redis publicly. |
| Backups | Download the `.zip` regularly and store it with `dockflare.key`. Both files are required to decrypt the configuration on restore. |
| Agents | Treat API keys like credentials. Deploy them with the socket proxy so only required Docker endpoints are exposed, and remember the container runs as the unprivileged `dockflare` user (UID/GID 65532); align host permissions or rebuild with matching `DOCKFLARE_UID/DOCKFLARE_GID`. |

View file

@ -1,10 +1,10 @@
{% extends "layout.html" %}
{% extends "base.html" %}
{% block title %}DockFlare is Restarting{% endblock %}
{% block content %}
<div class="flex flex-col items-center justify-center min-h-[60vh] text-center space-y-6">
<img src="{{ url_for('static', filename='images/logo.svg') }}" alt="DockFlare Logo" class="w-24 h-24 animate-pulse" />
<img src="{{ url_for('static', filename='images/logo.gif') }}" alt="DockFlare Logo" class="w-24 h-24 animate-pulse" />
<div class="space-y-2">
<h1 class="text-3xl font-semibold">Hold tight, DockFlare is rebooting...</h1>
<p class="text-lg opacity-80">Were loading your restored configuration and giving the tunnel hamsters a quick pep talk.</p>
@ -23,14 +23,33 @@
<script>
(function() {
const seconds = {{ countdown_seconds }};
const redirectUrl = "{{ url_for('auth.login') }}";
const pingUrl = "{{ url_for('web.ping') }}";
const countdownEl = document.getElementById('countdown');
const progressEl = document.getElementById('progress');
let remaining = seconds;
const pollForReady = () => {
fetch(pingUrl, { cache: 'no-store' })
.then(response => {
if (response.ok) {
window.location.replace(redirectUrl);
return;
}
setTimeout(pollForReady, 1000);
})
.catch(() => {
setTimeout(pollForReady, 1000);
});
};
const tick = () => {
remaining -= 1;
if (remaining < 0) {
window.location.reload();
clearInterval(timerId);
countdownEl.textContent = 0;
progressEl.value = seconds;
pollForReady();
return;
}
countdownEl.textContent = remaining;
@ -40,7 +59,7 @@
countdownEl.textContent = seconds;
progressEl.setAttribute('max', seconds);
progressEl.value = 0;
setInterval(tick, 1000);
const timerId = setInterval(tick, 1000);
})();
</script>
{% endblock %}

View file

@ -134,7 +134,7 @@ def restore_from_backup():
current_app.import_from_env = False
if is_full_archive:
return render_template('restore_restarting.html', countdown_seconds=5)
return render_template('restore_restarting.html', countdown_seconds=7)
return redirect(url_for('auth.login'))
except Exception as err: