A zero-database content management system for portfolio & personal sites.
One PHP file. One JSON file. No MySQL, no setup wizard, no bloat.
Most "simple" CMSs still drag in a database, a Composer dependency tree and a 600 MB container. For a portfolio site — a handful of sections, one admin, zero concurrent editors — that is absurd.
JSON CMS stores all content in a single data.json file. Your frontend
fetches it and renders. The admin panel edits it, and before every save it
keeps a timestamped backup. That is the whole system — and it is running in
production on real sites.
vantonopoulos.duckdns.org — a live portfolio site running on this exact code, in production.
- Single-file admin —
admin.phpis the entire backend: login, editor, uploader. - Timing-safe auth — passwords checked with
hash_equals(), session-based. - Automatic backups — every save snapshots
data.jsontobackups/(keeps the last 5). - Data-driven frontend — included demo theme renders entirely from
data.jsonviafetch(). - CV upload — replace your
cv.pdfstraight from the admin panel. - No build step — plain PHP 8.3 + vanilla JS. Works on any shared host.
- Docker-ready — one command to run it anywhere.
- ~50 KB total — the whole CMS is one readable PHP file.
┌────────────────────────────┐
browser ──────► │ index.html (frontend) │
│ fetch("data.json") │
└─────────────┬──────────────┘
│ reads
┌─────────────▼──────────────┐
│ data.json │ ◄── the database
└─────────────▲──────────────┘
│ writes (with backup first)
┌─────────────┴──────────────┐
admin.php ────► │ admin.php (auth + editor) │
(password) └─────────────┬──────────────┘
│ before each save
┌─────────────▼──────────────┐
│ backups/data.TIMESTAMP.json│ (last 5 kept)
└────────────────────────────┘
The included demo theme (site) and the admin panel:
git clone https://github.com/avasileios/json-cms.git
cd json-cms
ADMIN_PASS=super-secret docker compose up -d
# → http://localhost:8080 (admin at /admin.php)git clone https://github.com/avasileios/json-cms.git
cd json-cms
./install.sh # generates admin-config.php with a random password
php -S localhost:8000
# → http://localhost:8000 (admin at /admin.php)cp admin-config.sample.php admin-config.php # then EDIT THE PASSWORD
cp data.sample.json data.json
# point your web server at the project directoryserver {
listen 80;
server_name portfolio.example.com;
root /var/www/json-cms;
index index.html;
location / { try_files $uri $uri/ =404; }
# Optionally: protect the admin panel behind basic auth (extra layer)
location = /admin.php {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd-portfolio;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}<VirtualHost *:80>
ServerName portfolio.example.com
DocumentRoot /var/www/json-cms
<Directory /var/www/json-cms>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>json-cms/
├── admin.php # the entire CMS backend (auth, editor, uploads)
├── admin-config.sample.php # copy to admin-config.php and set your password
├── data.sample.json # demo content — copy to data.json
├── index.html # included demo frontend (terminal theme)
├── install.sh # one-command setup (PHP or Docker)
├── docker-compose.yml # PHP 8.3 + Apache, named volume for persistence
├── Dockerfile
├── backups/ # automatic timestamped snapshots (git-ignored)
└── README.md
- Open
admin.phpand log in with the password fromadmin-config.php. - Edit sections with tabs: Profile · Skills · Experience · Certs · Projects · Hobbies · CV.
- Click Αποθήκευση αλλαγών (Save) — the panel writes
data.jsonand snapshots the previous version intobackups/. - Your frontend picks the change up immediately (
fetchusesno-store).
The UI strings are in Greek (el) — the admin panel was built for Greek
business owners. Every string lives in admin.php; translate them freely.
- Change the default password immediately (
admin-config.php). - Serve the site over HTTPS — the admin panel sends the password over POST.
- Consider basic-auth (or an IP allowlist) in front of
admin.phpfor a second layer — see the Nginx snippet above. backups/should stay outside the web root or be denied by the server config — it contains full content snapshots.- Keep
admin-config.phpout of version control — it is already git-ignored. See SECURITY.md.
MIT © 2026 Vasileios Antonopoulos

