# MeEdu: a self-hosted online course platform built on PHP 7.4 and Laravel 8

> MeEdu is an open source knowledge-payment and online school system from Qsnh/meedu, shipped as a Docker Compose stack with a PHP API and three Vue front ends. The install is short, but the .env keys and the Apache 2.0 plus additional-terms licence are the parts to read before you commit.

**Qsnh/meedu** — MeEdu 是一款面向个人、中小机构的在线网校、知识付费、线上培训解决方案。

- Repository: https://github.com/Qsnh/meedu
- Website: https://www.meedu.vip
- Stars: 3,864 · Forks: 1,033
- Language: TypeScript
- License: Apache-2.0
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/qsnh-meedu

## What MeEdu solves, and for whom

Selling recorded courses means assembling a lot of unglamorous parts: student registration by phone number, a course catalogue, purchase flow, playback, learning statistics, and an admin area where a non-developer can edit the site. MeEdu packages that set as one deployable system. The README describes it as an open source online school and knowledge-payment solution built on PHP 7.4, Laravel 8, MySQL and Redis, with on-demand video, course purchase, site decoration, phone-number login and registration, learning statistics and role management.

The stated audience is individuals, small and mid-sized institutions, and online training operations. That framing matters, because the feature list is deliberately narrower than a full LMS. The README says the open source edition is the base, and that a commercial edition adds live classes, exams and practice, ebooks, image-and-text content, on-site Q&A, flash sales, group buying, redemption codes, plus WeChat Mini Program and Android and iOS app clients. So the free repository is the web platform, not the whole product line.

The architecture is front-end/back-end separated, with PC and H5 clients named in the README. The repository layout confirms it: xyz.meedu.api, xyz.meedu.admin, xyz.meedu.pc and xyz.meedu.h5 sit side by side at the top level. If you are evaluating this as a replacement for a hosted course platform, the relevant question is not whether it has every feature, but whether the open source slice covers your teaching format.

## How the Docker Compose stack is wired

The compose.yml file defines four services on one bridge network. meedu runs the image registry.cn-hangzhou.aliyuncs.com/meedu/light:4.9.32 and publishes four ports: 8000 for the API, 8100 for PC, 8200 for H5 and 8300 for the admin interface. It depends on mysql and redis. A fourth service, meilisearch, runs image registry.cn-hangzhou.aliyuncs.com/hzbs/meilisearch:0.24.0 and backs search.

Configuration flows through environment variables. The meedu service receives DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD, REDIS_HOST, REDIS_PASSWORD, REDIS_PORT, QUEUE_DRIVER, APP_KEY, JWT_SECRET, MEILISEARCH_HOST and MEILISEARCH_KEY, all interpolated from the .env file. MySQL and Redis are not exposed to the host by default; the ports: entries for 6379, 3306 and 7700 are commented out in compose.yml, so they stay reachable only inside meedu-network. That is a sensible default for a single-host deployment.

The Dockerfile shows how the image is assembled. A Node 20 Alpine stage copies xyz.meedu.admin, xyz.meedu.h5 and xyz.meedu.pc, runs pnpm i --frozen-lockfile and builds each with VITE_APP_URL=/api/. A second stage based on php:7.4-fpm-alpine copies the Nginx and PHP-FPM configuration from docker/, copies xyz.meedu.api into /var/www/api, then drops the three built front ends into /var/www/admin, /var/www/pc and /var/www/h5. It runs composer install --optimize-autoloader --no-dev, caches routes, links storage and runs php artisan install:lock. The container command waits 15 seconds, runs php artisan meedu:upgrade, locks the installer again, then starts nginx and php-fpm. That upgrade call on every start is the mechanism you will meet first when you pull a newer image.

## Installing MeEdu with docker-compose and reaching the admin panel

The README gives a three-step start. Clone the repository, copy the environment file, fill in two secrets, then bring the stack up. Note that the README's clone URL points at Gitee while the repository is mirrored on GitHub; either source gives the same tree.

```bash
git clone --branch main https://gitee.com/myteng/MeEdu.git meedu
cd meedu
cp .env.example .env
```

On Windows the README says to use copy .env.example .env instead. Next, edit .env. The README is explicit that APP_KEY and JWT_SECRET must both be generated by you and kept secret, and that leaving them blank or using public example values means cookies can be decrypted and tokens forged. Generate APP_KEY in the required base64:<32 bytes base64> form:

```bash
echo "base64:$(openssl rand -base64 32)"
```

Generate JWT_SECRET with 48 random bytes:

```bash
openssl rand -base64 48
```

Paste each output after the matching key in .env, with no space after the equals sign. The .env.example file also notes two Artisan equivalents: docker compose exec meedu php artisan key:generate and docker compose exec meedu php artisan jwt:secret. Then start the stack:

```bash
docker-compose up -d
```

The README says to wait about 30 seconds, after which the admin interface answers at http://localhost:8300, with PC at http://localhost:8100, H5 at http://localhost:8200 and the API at http://localhost:8000. If the admin page does not come up, the container command in the Dockerfile sleeps 15 seconds before running migrations, so give it a moment and then check the meedu container logs; the compose file caps json-file logs at 10 MB across 10 files, so history is bounded.

## Secrets and defaults you must change before exposing MeEdu

The .env.example ships working defaults for the infrastructure services: DB_USERNAME=root with DB_PASSWORD=meeduxyz, and REDIS_PASSWORD=F9nO2FzJ*%uDX58!. The compose file sets MYSQL_ROOT_PASSWORD=meeduxyz on the MySQL service. These are published values. The README's security section is only about vulnerability disclosure, telling reporters to email tengyongzhi@meedu.vip and expect a reply within 24 hours, so it does not cover hardening.

The practical consequence: if you publish port 8300 or 8100 to the internet without changing the database and Redis credentials, you are running a stack whose internal passwords are in the public repository. The compose file does not expose MySQL or Redis ports by default, which limits the blast radius to the meedu container itself, but the credentials still travel over the Docker network and appear in your .env. Change them, and change APP_KEY and JWT_SECRET as the README instructs.

There is a second operational detail worth knowing. The container command runs php artisan install:lock after meedu:upgrade, and the Dockerfile runs the same command at build time. That suggests an installer state is tracked and re-locked on boot, so a fresh container is expected to come up already installed. The README does not document what happens if you need to re-run the installer or reset that lock, and neither file explains rollback. Treat the lock as a state you should understand before you experiment on a live instance.

## Where MeEdu is the wrong tool

The open source edition does not include live classes, exams and practice, ebooks, image-and-text content, on-site Q&A, flash sales, group buying or redemption codes. The README places all of those in the commercial edition, together with WeChat Mini Program, Android and iOS clients. If your teaching model depends on scheduled live sessions or graded assessments, the free repository is the wrong starting point, because those are not features you can switch on; they are a different product.

Version currency is the other constraint. The stack pins PHP 7.4 and Laravel 8, and the Dockerfile builds on php:7.4-fpm-alpine. PHP 7.4 is old, and the image is served from an Aliyun registry, so pulls depend on reachability of registry.cn-hangzhou.aliyuncs.com. If your organisation requires a current PHP runtime or images from a registry you control, you are looking at either a fork or a rebuild, not a configuration change.

There is also a queue consideration. .env.example sets QUEUE_DRIVER=sync, which processes jobs inline. For a small site that is fine and removes a moving part. Under load, synchronous processing puts work like notifications directly in the request path. The compose file defines no separate worker service, so moving to an asynchronous driver means adding one yourself. The README does not describe that setup.

## How MeEdu compares with EduSoho and PlayEdu

The search terms people use around this project include EduSoho and PlayEdu, and the comparison is fair, since all three sit in the same Chinese online-school space. EduSoho is the older, commercially established option: its open source edition has historically been distributed under its own licence terms rather than a permissive one, and the product line leans toward larger institutions with more built-in marketing features. MeEdu's differentiator is the deployment shape. Everything is one docker-compose up, with the API, admin, PC and H5 front ends in a single image, so evaluation takes minutes rather than an afternoon of PHP environment work.

PlayEdu is the closer architectural neighbour, since it also targets self-hosting for training scenarios. The difference to check first is runtime and client coverage: MeEdu's README commits to PHP 7.4 and Laravel 8 with PC and H5 web clients in the open source edition, and pushes native apps and Mini Program support into the commercial tier. If your requirement is a mobile app rather than a mobile web page, that boundary decides the choice before any feature comparison does.

None of these numbers are settled by star counts or download figures, which say nothing about whether a stack fits your runtime policy or your teaching format. Read the licence, check the PHP version you would be running, and map the feature list against the commercial/community split before comparing anything else.

## Licence, upgrades and the cost of staying current

MeEdu is Apache-2.0 with a twist. The README states that the software follows the Apache 2.0 licence with additional commercial-use conditions, and that using it also requires following the terms in ADDITIONAL_TERMS.md, which is present at the repository root. Copyright is held by 杭州白书科技有限公司. The practical reading: Apache 2.0 alone would let you use the code commercially with minimal obligations, but the additional terms file is part of the deal, and the README does not summarise what it says. Read that file yourself; this is not legal advice, and the terms are the terms.

Upgrade cost is shaped by the release cadence. The most recent releases listed are v4.9.32 on 2026-06-15, v4.9.31 on 2026-05-26 and v4.9.30 on 2026-03-30, and the last push to the repository was on 2026-09-14. The compose file pins the image to 4.9.32, so a new release means editing that tag and restarting. Because the container command runs php artisan meedu:upgrade on every boot, migrations run automatically when the image changes. That is convenient and also the risk: there is no documented rollback path in the README, so a database snapshot before you change the tag is the only safety net the material describes.

Two smaller costs are worth pricing in. The front ends build with pnpm and are compiled into the image, so customising the PC or H5 templates means rebuilding rather than editing files in place. And the MySQL, Redis and Meilisearch images come from registry.cn-hangzhou.aliyuncs.com, which you will want to mirror if your network cannot reach it reliably.

## Conclusion

Adopt MeEdu if you want a working self-hosted course storefront without building the student login, purchase and admin layers yourself, and if you can live with PHP 7.4 plus Laravel 8 and the additional terms in ADDITIONAL_TERMS.md. Do not adopt it if you need live streaming, exams, ebooks or native mobile apps, because the README places those in the commercial edition. Before you deploy, generate APP_KEY and JWT_SECRET yourself, change the default MySQL root password and the Redis password from .env.example, and confirm that your intended use is covered by the licence.

## FAQ

### How do I install MeEdu and open the admin panel?

Clone the repository, copy .env.example to .env, fill in APP_KEY and JWT_SECRET, then run docker-compose up -d. After roughly 30 seconds the admin interface is at http://localhost:8300, with PC on 8100, H5 on 8200 and the API on 8000.

### What ports does MeEdu use by default?

The compose file publishes 8000 for the API, 8100 for the PC front end, 8200 for the H5 front end and 8300 for the admin interface. MySQL, Redis and Meilisearch ports are commented out and stay inside the meedu-network bridge.

### Does MeEdu include live classes, exams or mobile apps?

No. The README states that live classes, exams and practice, ebooks, image-and-text content, on-site Q&A, flash sales, group buying and redemption codes are part of the commercial edition, along with WeChat Mini Program and Android and iOS clients. The open source edition covers on-demand video, course purchase, site decoration, phone-number login and registration, learning statistics and role management.

### How do I generate APP_KEY and JWT_SECRET for MeEdu?

The README gives openssl rand -base64 32 prefixed with base64: for APP_KEY, and openssl rand -base64 48 for JWT_SECRET, or the Artisan commands php artisan key:generate and php artisan jwt:secret inside the meedu container. Both values must be generated by you and kept secret, because leaving them blank or using public example values allows cookie decryption and token forgery.

## Sources

- [License: Apache-2.0](https://github.com/Qsnh/meedu/blob/main/LICENSE)
- [Project website](https://www.meedu.vip)
- [Qsnh/meedu on GitHub](https://github.com/Qsnh/meedu)
- [README](https://github.com/Qsnh/meedu/blob/main/README.md)
- [Releases](https://github.com/Qsnh/meedu/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/qsnh-meedu
