LeafCMS GitHub Install
Docs

The in-app guide

This is the same guide as Admin → Help: usage, each admin screen, themes, tags, environment, and install.

What you need to go live

php artisan serve is for trying locally. A real site needs PHP, a database, and nginx (or similar) pointing at public.

PHP

PHP 8.2+ with pdo, mbstring, openssl, tokenizer, xml, ctype, json, fileinfo, curl. Add pdo_mysql for MySQL or pdo_sqlite for SQLite.

Watermarks and thumbs need gd or imagick. The rest still installs without them.

Suggested php.ini:

memory_limit = 256M
upload_max_filesize = 32M
post_max_size = 32M
max_execution_time = 60
date.timezone = Asia/Shanghai

Restart php-fpm after changes. storage and bootstrap/cache must be writable, and you should have run php artisan storage:link.

MySQL

Prefer MySQL 5.7 / 8.0 or MariaDB 10.3+, charset utf8mb4. Create an empty database, then put the account in .env.

CREATE DATABASE cms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'cms'@'127.0.0.1' IDENTIFIED BY 'use-a-strong-password';
GRANT ALL ON cms.* TO 'cms'@'127.0.0.1';
FLUSH PRIVILEGES;
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=cms
DB_USERNAME=cms
DB_PASSWORD=use-a-strong-password

Trying locally can stay on SQLite (DB_CONNECTION=sqlite). After editing .env, run php artisan config:clear.

nginx

The document root must be public, not the project root. Replace /var/www/leafcms and the PHP-FPM socket.

server {
    listen 80;
    server_name www.example.com;
    root /var/www/leafcms/public;
    index index.php;
    charset utf-8;
    client_max_body_size 32m;

    location / {
        try_files /html/site_1$uri/index.html /html/site_1$uri $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

The first try_files line prefers disk HTML from the admin. Missing files go to PHP. For extra sites, change site_1. Terminate HTTPS in front or in your panel.

Set APP_URL=https://www.example.com, APP_ENV=production, APP_DEBUG=false. Behind a reverse proxy, also set TRUST_PROXIES=*.

Search engines and robots.txt

The front URL is /robots.txt. By default the home page is allowed; /admin, /install, /member, and /preview are not, and a sitemap line is included for each language. While the site is closed the whole file is Disallow: /.

To block extra paths, add “robots.txt extra rules” under Settings → More, for example:

Disallow: /private
Disallow: /drafts

No HTML tags. About 40 lines, 200 characters each. Check the live /robots.txt after saving. This is not the same as the page <meta name="robots">. After install, pick which sections and entries to list under Operations → More → Sitemap.

Object storage for uploads

Files go to storage/app/public by default (php artisan storage:link). To only change the public host, set the CDN field under Settings → More.

For Aliyun OSS / Tencent COS / AWS S3: composer require league/flysystem-aws-s3-v3, then in .env:

CMS_MEDIA_DISK=s3
AWS_ACCESS_KEY_ID=your-key
AWS_SECRET_ACCESS_KEY=your-secret
AWS_DEFAULT_REGION=oss-cn-hangzhou
AWS_BUCKET=bucket
AWS_URL=https://bucket.oss-cn-hangzhou.aliyuncs.com
AWS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
AWS_USE_PATH_STYLE_ENDPOINT=false

Remote disks skip local GD watermarks. Run php artisan config:clear after changing env.

nginx in front of Docker

If the container only publishes 8000, the browser sees the Docker gateway IP. Put nginx in front and pass the real client IP:

location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;
}

The container also needs TRUST_PROXIES=* (already in this repo’s docker-compose.yml). Port mapping alone, with no nginx, cannot see the visitor’s public IP.

Collectors and analytics prune need Laravel woken every minute — see Schedule.

Group @sanpang