| Feature | Description |
|---|---|
| π PHP 8+ | With OPcache pre-enabled β runs 2xβ3x faster out of the box |
| π Apache | Configured with mod_rewrite and .htaccess support |
| ποΈ MariaDB | Full database engine with phpMyAdmin UI at /sql |
| π File Manager | TinyFileManager at /files |
| π» Web Terminal | Custom browser-based shell at /terminal β no SSH needed |
| βοΈ .env Support | Auto-loads /var/www/localhost/htdocs/.env at startup |
| πΎ Persistent Storage | /data mount required β database is stored here |
| π Non-root | Runs as user 1000 for improved security |
| π³ Alpine Base | Ultra-lightweight image with minimal footprint |
| βοΈ HF Spaces Ready | Works seamlessly on Hugging Face Docker Spaces |
| π Custom Domain | Mask your HF URL with a custom domain via Cloudflare Workers |
Alpine Linux (latest)
βββ Apache 2 β Web server (port 7860)
βββ PHP 8.4
βββ MariaDB β Database server
βββ phpMyAdmin β Database UI at /sql
βββ TinyFileManager β File manager at /files
βββ Web Terminal β Custom shell UI at /terminal
βββ Composer β PHP dependency manager
βββ Git, Nano, Wget, Zip, Unzip, ImageMagick
| Extension | Purpose |
|---|---|
| php-mysqli | MySQL / MariaDB direct connection |
| php-pdo | PHP Data Objects β database abstraction base |
| php-pdo_mysql | PDO driver for MySQL / MariaDB |
| php-pdo_pgsql | PDO driver for PostgreSQL |
| php-mbstring | Multibyte string handling β required by most frameworks |
| php-xml | XML parsing and generation |
| php-simplexml | Simple XML object interface β used by WordPress and APIs |
| php-dom | Full DOM XML/HTML parsing β required by Laravel and Symfony |
| php-xmlwriter | Writing XML documents programmatically |
| php-xmlreader | Streaming XML reader for large files |
| php-xsl | XSLT transformations |
| php-gd | Image creation and manipulation (resize, crop, watermark) |
| php-imagick | Advanced image processing via ImageMagick |
| php-exif | Read image metadata (camera, GPS, dimensions) |
| php-curl | HTTP requests β required by APIs, Guzzle, SDKs |
| php-session | Session management |
| php-opcache | Bytecode caching β makes PHP 2xβ3x faster |
| php-phar | PHP Archive support β required by Composer |
| php-openssl | SSL/TLS encryption, JWT, secure hashing |
| php-sodium | Modern cryptography library |
| php-iconv | Character encoding conversion |
| php-mbstring | Multibyte / Unicode string support |
| php-json | JSON encode/decode |
| php-zip | Create and extract ZIP archives |
| php-bz2 | Bzip2 compression support |
| php-intl | Internationalization β dates, currencies, locales |
| php-gettext | Translations and i18n support |
| php-bcmath | Arbitrary precision math β required by payment gateways |
| php-gmp | GNU Multiple Precision β cryptography and big numbers |
| php-apcu | In-memory user cache β speeds up repeated operations |
| php-redis | Redis cache and session driver |
| php-soap | SOAP web services client and server |
| php-ldap | LDAP authentication and directory services |
| php-ctype | Character type checking functions |
| php-fileinfo | Detect file MIME types |
| php-tokenizer | PHP code tokenizer β required by Composer and Laravel |
| php-sockets | Low-level socket programming and WebSocket support |
| php-posix | POSIX process functions |
| php-pcntl | Process control β fork, signals, process management |
| php-ftp | FTP client functions |
| php-calendar | Calendar and date conversion functions |
| php-shmop | Shared memory read/write |
| php-sysvmsg | System V message queues |
| php-sysvsem | System V semaphores |
| php-sysvshm | System V shared memory |
| php-tidy | HTML cleanup and repair |
| php-readline | Interactive PHP shell (php -a) support |
β Recommended β Free hosting, no server required!
my-php-server)β οΈ Keep your Space set to Public. Hugging Face requires the Space to be public for it to run continuously and be accessible via a URL. Private Spaces may sleep or become inaccessible depending on your plan.
Only the Dockerfile is needed in your Space repository:
your-space/
βββ Dockerfile β
this is the only file needed here
π‘ Drag & drop the
Dockerfilein the Files tab, or push via Git:
git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
cd YOUR_SPACE_NAME
# Only add the Dockerfile here
git add Dockerfile
git commit -m "Add Dockerfile"
git push
β οΈ Do NOT place your project files here.
Once the Space is live, upload your project files using the File Manager at/files.
Simply go to/filesβ navigate to/var/www/localhost/htdocsβ upload your files there.
On Hugging Face, environment variables are configured in Space Settings β not in the command line.
| Variable | Default | Description |
|---|---|---|
MYSQL_USER |
admin |
Database username |
MYSQL_PASSWORD |
admin |
Database password (change this!) |
MYSQL_DATABASE |
admin |
Database name |
SQL_PATH |
sql |
URL path for phpMyAdmin β e.g. mysecretdb opens at /mysecretdb |
FILES_PATH |
files |
URL path for File Manager |
TERMINAL_PATH |
terminal |
URL path for Web Terminal |
This step is mandatory β without it, your database will reset on every restart.
Go to Space Settings β Persistent Storage and add:
/dataFull setup details β Persistent Storage section
Hugging Face will build and deploy automatically. Your live URL:
https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space/
For those who prefer their own server or want to test locally.
git clone https://github.com/YOUR_USERNAME/YOUR_REPO_NAME.git
cd YOUR_REPO_NAME
docker build -t php-lamp .
docker run -d \
-p 7860:7860 \
-e MYSQL_USER=admin \
-e MYSQL_PASSWORD=yourpassword \
-e MYSQL_DATABASE=mydb \
--name php-lamp \
php-lamp
# docker-compose.yml
version: '3.8'
services:
php-lamp:
build: .
ports:
- "7860:7860"
environment:
- MYSQL_USER=admin
- MYSQL_PASSWORD=yourpassword
- MYSQL_DATABASE=mydb
restart: unless-stopped
docker compose up -d
http://localhost:7860/ β Local machine
http://YOUR_VPS_IP:7860/ β Remote VPS
β οΈ On VPS: Open port
7860in your firewall first:sudo ufw allow 7860 # Ubuntu / Debian
| Tool | Default URL | Env Variable |
|---|---|---|
| π Website | / |
β |
| ποΈ Database UI | /sql |
SQL_PATH |
| π File Manager | /files |
FILES_PATH |
| π» Web Terminal | /terminal |
TERMINAL_PATH |
Web root directory:
/var/www/localhost/htdocs
All tool paths are fully customizable via environment variables β set them in Space Settings (or .env) to hide the default URLs from public discovery. For example, setting SQL_PATH=x7k2mdb means phpMyAdmin is only accessible at /x7k2mdb.
Username : admin
Password : admin
Database : admin
Host : 127.0.0.1
Port : 3306
β οΈ Change the password before going live on a public server!
On Hugging Face: Space β Settings β Variables and Secrets β update the values.
On VPS / Local:
Update ENV variables in your docker run command or docker-compose.yml.
π¨ This step is REQUIRED β the database will not work without it!
MariaDB stores all its data at /data/mysql inside the container. If /data is not mounted as persistent storage, the entire database is wiped every time the Space restarts or rebuilds.
| Field | Value |
|---|---|
| Permission | Read & Write |
| Mount path | /data |
| Storage size | Choose as needed (free tier: up to 50GB) |
| Visibility | Private (your data stays secure) |
π Always keep your Persistent Storage bucket set to Private. The storage bucket holds your entire database β keeping it private ensures that no one else can access or browse your data files. Your Space itself can remain Public (so your website is accessible), while the storage bucket stays Private (so your database is protected). These are two separate settings β one does not affect the other.
Once mounted, the /data/mysql directory will survive restarts and rebuilds β your database tables and data are fully preserved automatically. No extra configuration needed.
The container handles everything internally on first boot:
# First run β initializes the database at /data/mysql
mariadb-install-db --datadir=/data/mysql --skip-test-db --user=1000
# Every run β starts MariaDB using /data/mysql as datadir
mariadbd --datadir=/data/mysql --bind-address=127.0.0.1
π‘ You donβt need to run these manually β
start.shdoes it automatically.
You can configure your PHP app using a .env file β no need to hardcode sensitive values like API keys or database credentials in your code.
Place a .env file in your web root:
/var/www/localhost/htdocs/.env
The server automatically loads it at startup, before Apache and MariaDB start. All variables are then available to your PHP app via getenv() or $_ENV.
.env File# App config
APP_NAME=MyApp
APP_ENV=production
APP_DEBUG=false
# Third-party API keys
STRIPE_KEY=sk_live_xxxxxxxxxxxx
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=587
MAIL_USERNAME=your@email.com
MAIL_PASSWORD=yourpassword
$appName = getenv('APP_NAME');
$stripe = getenv('STRIPE_KEY');
// Or via $_ENV superglobal
$debug = $_ENV['APP_DEBUG'];
KEY=value format# for comments β # this is a comment= β KEY=value β
Β KEY = value βKEY=value # comment β (comment becomes part of value).env to Git β add it to .gitignoreπ‘
.envvariables are loaded after theMYSQL_USER,MYSQL_PASSWORD, andMYSQL_DATABASEenv vars set in Space Settings β so they can override them if needed.
This setup uses TinyFileManager β accessible at /files.
By default, authentication is disabled for easy development.
/terminal) or File Manager (/files)/usr/share/webapps/filemanager/
index.php and find:
$use_auth = false;
$use_auth = true;
Username : admin
Password : admin@123
π‘ Change the password via the Settings βοΈ icon inside TinyFileManager.
A custom PHP-powered browser shell is available at /terminal β no SSH required.
free -h # RAM usage (Total / Used / Free)
df -h # Total disk storage
du -sh * # File sizes in current folder
pwd # Current directory
ls -la # List files with permissions
cd folder # Navigate into a folder
php -v # Check PHP version
composer -v # Check Composer version
git --version # Check Git version
unzip file.zip # Extract a zip archive
nano file.php # Edit a file in terminal
π Hide your Hugging Face URL β Serve your app from your own domain (e.g.
yoursite.com) while the actual server stays on Hugging Face behind the scenes.
This method uses a Cloudflare Worker as a reverse proxy. All traffic hits your domain first, gets forwarded to your HF Space, and the response is served back β visitors never see the *.hf.space URL.
Prerequisites:
If your domain is not already on Cloudflare:
alice.ns.cloudflare.com)my-php-proxy)export default {
async fetch(request) {
try {
const url = new URL(request.url);
// π Replace this with your actual Hugging Face Space URL
const backendHost = "YOUR_USERNAME-YOUR_SPACE_NAME.hf.space";
const backendUrl = new URL(request.url);
backendUrl.hostname = backendHost;
backendUrl.protocol = "https:";
const newHeaders = new Headers(request.headers);
newHeaders.set("X-Forwarded-Host", url.hostname);
newHeaders.set("X-Forwarded-Proto", "https");
if (newHeaders.has("origin")) {
newHeaders.set("origin", `https://${backendHost}`);
}
if (newHeaders.has("referer")) {
try {
const referer = new URL(newHeaders.get("referer"));
referer.hostname = backendHost;
referer.protocol = "https:";
newHeaders.set("referer", referer.toString());
} catch {}
}
const body = request.method === "GET" || request.method === "HEAD"
? undefined
: await request.arrayBuffer();
const response = await fetch(new Request(backendUrl.toString(), {
method: request.method,
headers: newHeaders,
body: body,
redirect: "manual",
}));
const headers = new Headers();
for (const [key, value] of response.headers.entries()) {
if (key.toLowerCase() === "set-cookie") continue;
if (key.toLowerCase() === "location") {
try {
const loc = new URL(value);
loc.hostname = url.hostname;
loc.protocol = url.protocol;
headers.set("location", loc.toString());
} catch {
headers.set("location", value);
}
continue;
}
headers.set(key, value);
}
for (const cookie of response.headers.getAll("set-cookie")) {
headers.append("set-cookie", cookie
.replace(/;\s*Domain=[^;]*/gi, "")
.replace(/;\s*SameSite=[^;]*/gi, "")
.concat("; SameSite=Lax")
);
}
// Security headers
headers.delete("x-powered-by");
headers.delete("server");
headers.delete("cf-cache-status");
headers.set("X-Frame-Options", "SAMEORIGIN");
headers.set("X-Content-Type-Options", "nosniff");
headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
headers.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
headers.set("X-XSS-Protection", "1; mode=block");
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers,
});
} catch (err) {
return new Response(`Worker Error: ${err.message}`, { status: 500 });
}
},
};
Important: Replace YOUR_USERNAME-YOUR_SPACE_NAME.hf.space on line 6 with your actual HF Space URL.
For example: shkumaraman-backend.hf.space
Click βDeployβ β
You need to complete both parts below β Simple domain mapping AND the Route pattern. Both are required for the worker to handle your domain correctly.
This registers your domain with the Worker directly.
*.yourdomain.com/*
π‘ The
*.yourdomain.com/*pattern covers all subdomains and all paths automatically. Do not change it unless you need a more specific rule.
Your custom domain now proxies all traffic to your Hugging Face Space.
https://yourdomain.com/ β Your website
https://yourdomain.com/sql β phpMyAdmin
https://yourdomain.com/files β File Manager
https://yourdomain.com/terminal β Web Terminal
The original *.hf.space URL still works too β the Worker doesnβt disable it, it just adds your custom domain on top.
| Feature | What it does |
|---|---|
| Proxy | Forwards all requests to your HF Space and returns the response |
| URL masking | Rewrites Location headers on redirects so visitors stay on your domain |
| Cookie handling | Strips Domain and SameSite attributes so cookies work cross-domain |
| Security headers | Adds HSTS, X-Frame-Options, X-Content-Type-Options, and more |
| Header cleanup | Removes x-powered-by and server headers to reduce fingerprinting |
Domain not routing to the worker?
Getting a Cloudflare error page?
backendHost in the worker code β it must match your exact HF Space URL (no https://, no trailing slash)your-username-your-space.hf.spaceCookies or sessions not working?
Worker only needed on one subdomain?
app.yourdomain.com/* instead of *.yourdomain.com/*A record pointing to 192.0.2.1 as a placeholder β Cloudflareβs proxy will intercept it before it reaches that IP)/var/www/localhost/htdocs/data in Space Settings β without it, database resets on every restart.zip via File Manager and extract it directly on the serverMYSQL_PASSWORDContributions, issues, and feature requests are welcome!
git checkout -b feature/amazing-featuregit commit -m 'Add amazing feature'git push origin feature/amazing-feature
Please Wait