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
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.git
.git/**
/contrib/container/config.php
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
*.php text eol=lf
*.json text eol=lf
contrib/container/Dockerfile text eol=lf
contrib/container/container-entrypoint text eol=lf
contrib/container/container-healthcheck text eol=lf
contrib/container/hash-password text eol=lf
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ Tinyfilemanager is highly documented on the [wiki pages](https://github.com/pras
- PHP 5.5.0 or higher.
- Fileinfo, iconv, zip, tar and mbstring extensions are strongly recommended.

## Containers

The maintained Apache/PHP container, Compose file, and quick start are in
[`contrib/container`](contrib/container/README.md).

## How to use

Download ZIP with latest version from master branch.
Expand Down
1 change: 1 addition & 0 deletions contrib/container/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/config.php
69 changes: 69 additions & 0 deletions contrib/container/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# syntax=docker/dockerfile:1@sha256:ecfaec9ed6d810b56388c508f4121597bfbba70d41a6dfeee4d8cad5f295fc32

ARG PHP_IMAGE=php:8.3.33-apache-bookworm@sha256:69e641a40929c9db9d1ce43b982d46f6701ccdd5f7b4c32d5bd773cae6882393
FROM ${PHP_IMAGE}

LABEL org.opencontainers.image.title="Tiny File Manager" \
org.opencontainers.image.description="Local-filesystem Tiny File Manager service" \
org.opencontainers.image.source="https://github.com/prasathmani/tinyfilemanager" \
org.opencontainers.image.lifecycle="trusted-local-non-production"

# Build ZipArchive and retain only its runtime libraries. The other recommended
# extensions are already present in the pinned official PHP image.
RUN set -eux; \
saved_apt_mark="$(apt-mark showmanual)"; \
apt-get update; \
apt-get install -y --no-install-recommends libzip-dev=1.7.3-1+b1; \
docker-php-ext-install -j"$(nproc)" zip; \
apt-mark auto '.*' > /dev/null; \
if [ -n "$saved_apt_mark" ]; then apt-mark manual $saved_apt_mark; fi; \
find /usr/local -type f -executable -exec ldd '{}' ';' \
| awk '/=>/ { library = $(NF-1); if (index(library, "/usr/local/") == 1) next; gsub("^/(usr/)?", "", library); print "*" library }' \
| sort -u \
| xargs -r dpkg-query --search \
| awk 'sub(":$", "", $1) { print $1 }' \
| sort -u \
| xargs -r apt-mark manual; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
rm -rf /var/lib/apt/lists/*; \
php -r 'foreach (["fileinfo", "iconv", "mbstring", "posix", "zip"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "missing PHP extension: $extension\n"); exit(1); } }'

COPY tinyfilemanager.php /var/www/html/index.php
COPY translation.json /var/www/html/translation.json
COPY contrib/container/healthz.php /var/www/html/healthz.php
COPY LICENSE /usr/share/licenses/tinyfilemanager/LICENSE
COPY contrib/container/apache.conf /etc/apache2/conf-available/tinyfilemanager.conf
COPY contrib/container/vhost.conf /etc/apache2/sites-available/tinyfilemanager.conf
COPY contrib/container/php.ini /usr/local/etc/php/conf.d/tinyfilemanager.ini
COPY contrib/container/container-entrypoint /usr/local/bin/container-entrypoint
COPY contrib/container/container-healthcheck /usr/local/bin/container-healthcheck
COPY contrib/container/hash-password /usr/local/bin/tinyfilemanager-hash-password
COPY contrib/container/validate-config.php /usr/local/lib/tinyfilemanager/validate-config.php

RUN set -eux; \
rm -f /var/www/html/index.html; \
install -d -o www-data -g www-data -m 0770 /srv/tinyfilemanager/data; \
install -m 000 /dev/null /var/www/html/config.php; \
chown -R root:root /var/www/html /usr/local/lib/tinyfilemanager; \
find /var/www/html /usr/local/lib/tinyfilemanager -type d -exec chmod 0555 '{}' +; \
find /var/www/html /usr/local/lib/tinyfilemanager -type f ! -name config.php -exec chmod 0444 '{}' +; \
chmod 0555 /usr/local/bin/container-entrypoint \
/usr/local/bin/container-healthcheck \
/usr/local/bin/tinyfilemanager-hash-password; \
a2enmod headers; \
a2enconf tinyfilemanager; \
a2dissite 000-default; \
a2ensite tinyfilemanager; \
php -l /var/www/html/index.php; \
php -l /var/www/html/healthz.php; \
php -l /usr/local/lib/tinyfilemanager/validate-config.php; \
apache2ctl configtest

VOLUME ["/srv/tinyfilemanager/data"]
EXPOSE 80

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD ["/usr/local/bin/container-healthcheck"]

ENTRYPOINT ["/usr/local/bin/container-entrypoint"]
CMD ["apache2-foreground"]
21 changes: 21 additions & 0 deletions contrib/container/Dockerfile.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
**

!tinyfilemanager.php
!translation.json
!LICENSE
!contrib/
!contrib/container/
!contrib/container/apache.conf
!contrib/container/container-entrypoint
!contrib/container/container-healthcheck
!contrib/container/hash-password
!contrib/container/healthz.php
!contrib/container/php.ini
!contrib/container/validate-config.php
!contrib/container/vhost.conf

# Runtime configuration and credentials must never enter the build context.
contrib/container/config.php
**/*.key
**/*.pem
**/secrets/**
184 changes: 184 additions & 0 deletions contrib/container/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Tiny File Manager container

This builds a local-filesystem Tiny File Manager service. It requires an
external configuration file, stores managed files outside Apache's document
root, and listens on `127.0.0.1:8080` by default.

## Requirements

- Linux: a current Docker Engine with BuildKit and the `docker compose` plugin.
- macOS or Windows: a current Docker Desktop using Linux containers.

Run all commands from the repository root and include the shown `-f` option.

Choose a unique project name for this deployment. Compose refuses to run
without it, preventing separate checkouts from sharing a data volume:

Linux or macOS:

```sh
export TFM_PROJECT=tinyfilemanager-local
```

Windows PowerShell:

```powershell
$env:TFM_PROJECT = "tinyfilemanager-local"
```

## Build

```sh
docker compose -f contrib/container/compose.yaml build
```

## Configure

Create the ignored runtime configuration:

Linux or macOS:

```sh
cp contrib/container/config.php.example contrib/container/config.php
```

Windows PowerShell:

```powershell
Copy-Item contrib/container/config.php.example contrib/container/config.php
```

Generate a password hash without putting the password in the command line:

```sh
docker compose -f contrib/container/compose.yaml run --rm --no-deps --entrypoint tinyfilemanager-hash-password tinyfilemanager
```

Replace `REPLACE_WITH_PASSWORD_HASH` in `config.php` with the generated hash.
Edit the user name, read-only users, and time zone as needed. The application
upload limit may be lowered in `config.php`; raising the 256 MiB server limit
also requires editing `php.ini` and rebuilding. Keep `$root_path` at
`/srv/tinyfilemanager/data`; startup rejects missing credentials, placeholder
credentials, and baseline-policy mismatches. Per-user directory mappings are
not supported by this image.

The supplied configuration disables settings changes, direct links, media
previews, online document viewing, and URL uploads. Managed files remain
outside Apache's document root and downloads continue through the authenticated
application.

### Serve an existing host directory

Tiny File Manager's `$root_path` is separate from Apache's `DocumentRoot`.
Keep `$root_path` at `/srv/tinyfilemanager/data` and point that path at any
dedicated, unshared host directory with the supplied Compose override. Choose
this mode instead of the Docker-managed volume described below; do not mix the
commands for the two modes.

Linux:

These UID commands assume rootful Docker. With rootless or
user-namespace-remapped Docker, use the named volume unless UID 33 is mapped to
the correct host UID. For a new directory, `mkdir` must succeed; stop and review
permissions instead of continuing if the path already exists.

```sh
sudo mkdir -m 0750 /srv/tinyfilemanager-data &&
sudo chown 33:33 /srv/tinyfilemanager-data &&
export TFM_DATA_PATH=/srv/tinyfilemanager-data
```

For an existing dedicated tree, inspect and back up its ownership and ACLs,
then grant numeric UID 33 read, write, and directory-search access with the
host's ACL tools. Do not use recursive `chmod 0777` or blindly change ownership.

macOS:

```sh
mkdir -p "$HOME/tinyfilemanager-data"
export TFM_DATA_PATH="$HOME/tinyfilemanager-data"
```

Windows PowerShell:

```powershell
New-Item -ItemType Directory -Force "$HOME\tinyfilemanager-data" | Out-Null
$env:TFM_DATA_PATH = "$HOME\tinyfilemanager-data"
```

On macOS and Windows, ensure Docker Desktop can share the selected path. The
current host user must have read and write permission. If a Windows home
directory is redirected to a network share, select a local drive path instead.

On SELinux hosts, the override requests a private `Z` relabel. Never select
`/`, an entire home or system directory, or a path shared with another service
or container. Keep both `TFM_PROJECT` and `TFM_DATA_PATH` set in the same shell
for every host-directory command.

Start with the host directory:

```sh
docker compose -f contrib/container/compose.yaml -f contrib/container/compose.host-path.yaml up -d
```

Use both `-f` options for later `ps`, `logs`, and `down` commands. Do not mount
managed files below `/var/www/html`, where Apache could serve them without Tiny
File Manager authentication. Startup fails if the selected directory is not
readable, writable, and searchable by the Apache worker.

For a TLS reverse proxy, set `$container_tls_proxy = true` so session cookies
are marked `Secure`. Keep the container port on loopback, and configure the
proxy to replace rather than append forwarded headers.

## Start with a Docker-managed volume

Use this mode only when `TFM_DATA_PATH` is not being used:

```sh
docker compose -f contrib/container/compose.yaml up -d
docker compose -f contrib/container/compose.yaml ps
```

Open <http://127.0.0.1:8080>. To use another host port, set `TFM_PORT`, for
example:

Linux or macOS:

```sh
export TFM_PORT=8081
```

Windows PowerShell:

```powershell
$env:TFM_PORT = "8081"
```

Then run the applicable `up -d` command again.

View logs or stop the service with:

```sh
docker compose -f contrib/container/compose.yaml logs -f
docker compose -f contrib/container/compose.yaml down
```

The named data volume survives `down`. Running `down -v` deletes it.
`down -v` does not delete a bind-mounted host directory.

The built-in healthcheck is PHP/HTTP liveness only; it does not attest login,
data integrity, or production readiness.

## Scope

- This is a trusted-local, non-production image. Tiny File Manager follows
filesystem symlinks, so the data mount is not a confinement boundary. Do not
use restored/untrusted trees or trees containing symlinks.
- `config.php` is trusted executable PHP. Start from the supplied example; do
not mount configuration obtained from another user or system.
- The image has no TLS terminator. Keep the loopback binding or use the TLS
proxy mode above; do not expose it directly to an untrusted network.
- The current UI loads assets from public CDNs, so this image is unsuitable
for controlled or protected data.
- Never put passwords, private keys, or other secrets in the image or build
context. Mount runtime configuration read-only.
25 changes: 25 additions & 0 deletions contrib/container/apache.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
ServerTokens Prod
ServerSignature Off
ServerName localhost
TraceEnable Off
FileETag None
DirectoryIndex index.php

<Directory "/var/www/html">
Options -Indexes -Includes -ExecCGI
AllowOverride None
Require all granted
</Directory>

<Files "config.php">
Require all denied
</Files>

<Files "translation.json">
Require all denied
</Files>

Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "same-origin"
Header always set Permissions-Policy "camera=(), geolocation=(), microphone=()"
9 changes: 9 additions & 0 deletions contrib/container/compose.host-path.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
tinyfilemanager:
volumes:
- type: bind
source: "${TFM_DATA_PATH:?Set TFM_DATA_PATH to an existing absolute host directory}"
target: /srv/tinyfilemanager/data
bind:
create_host_path: false
selinux: Z
42 changes: 42 additions & 0 deletions contrib/container/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: ${TFM_PROJECT:?Set TFM_PROJECT to a unique Compose project name}

services:
tinyfilemanager:
image: tinyfilemanager:${TFM_PROJECT}
build:
context: ../..
dockerfile: contrib/container/Dockerfile
init: true
restart: unless-stopped
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- DAC_OVERRIDE
- KILL
- NET_BIND_SERVICE
- SETGID
- SETUID
ports:
- "127.0.0.1:${TFM_PORT:-8080}:80"
volumes:
- type: bind
source: ./config.php
target: /var/www/html/config.php
read_only: true
bind:
create_host_path: false
selinux: Z
- type: volume
source: data
target: /srv/tinyfilemanager/data
tmpfs:
- /run:rw,nosuid,nodev,size=16m,mode=0755
- /tmp:rw,noexec,nosuid,nodev,size=256m,mode=1777
stop_grace_period: 20s

volumes:
data:
Loading