Self-hosted project management with tasks, Gantt planning, time tracking, and team collaboration
77
Myelin is a full-stack, self-hosted project and delivery platform with task tracking, resource planning, time tracking, and multi-tenant organization support.
Version 1.0 β production-ready core (projects, planning, time, tickets, reporting, integrations). Report bugs and feature requests on GitHub.
pt_... tokens for webhooks, Workers, and integrations (Profile β API Tokens)/uploads/editor (URL references; base64 fallback only if upload is unavailable)/reporting)pnpm run desktop:dev)ensureAiAssistantViews (see server/utils/aiAssistantViews.ts)/docs./reports redirects to Reporting β Extract; manager analytics live under /reporting.http://localhost:3000/api-docs when the server is running.| Component | Technology |
|---|---|
| Frontend | Next.js 16, React 19, TypeScript, Tailwind CSS, Lucide icons |
| Rich Text | Tiptap (StarterKit, Image, Placeholder extensions) |
| Backend | Node.js 20, Express.js, TypeScript |
| Database | MySQL 8.0 or MSSQL (SQL Server) β portable SQL via server/config/database.ts |
| Cache (optional) | Redis 7 (ioredis), disabled when REDIS_ENABLED=false |
| Auth | JWT + personal API tokens (pt_...) |
| Real-time | Socket.io |
| Integrations | Jira / GitHub / Gitea REST APIs, Microsoft Graph (Outlook), AES-256-CBC encryption |
| Container | Alpine-based Node.js image |
git clone https://github.com/tiag0ss/myelin.git
cd myelin
pnpm install
cp .env.example .env
# Edit .env (database, JWT_SECRET, etc.)
Run the app (Express custom server embeds Next.js on the same port):
pnpm run dev
Open http://localhost:3000, complete the install wizard on first run, then sign in.
Production after pnpm run build:
pnpm start
# same as: pnpm run start:prod
Optional desktop shell:
pnpm run desktop:dev
Run tests:
pnpm test
cp .env.docker.example .env.docker
# Edit .env.docker with your values
MySQL example (default):
DB_PROVIDER=mysql
DB_HOST=mysql
DB_PORT=3306
DB_USER=appuser
DB_PASSWORD=your-strong-password-here
DB_NAME=projectmanagement
DB_CONNECTION_LIMIT=50
JWT_SECRET=your-super-secret-jwt-key-minimum-64-characters
ALLOWED_ORIGINS=http://localhost:3000,https://yourdomain.com
NEXT_PUBLIC_API_URL=http://localhost:3000
API_URL=http://localhost:3000
LOG_LEVEL=warn
MSSQL example:
DB_PROVIDER=mssql
DB_HOST=your-sqlserver-host
DB_PORT=1433
DB_USER=sa
DB_PASSWORD=your-strong-password-here
DB_NAME=projectmanagement
DB_CONNECTION_LIMIT=50
DB_ENCRYPT=false
DB_TRUST_SERVER_CERT=true
JWT_SECRET=your-super-secret-jwt-key-minimum-64-characters
ALLOWED_ORIGINS=http://localhost:3000
NEXT_PUBLIC_API_URL=http://localhost:3000
API_URL=http://localhost:3000
LOG_LEVEL=warn
Optional Redis (faster reads; MySQL/MSSQL remain source of truth):
REDIS_ENABLED=true
REDIS_URL=redis://redis:6379
REDIS_KEY_PREFIX=pm:
REDIS_DEFAULT_TTL_SECONDS=300
Set ENCRYPTION_KEY (64-char hex) in production if you use Jira, Outlook, or other encrypted integration settings. If omitted, JWT_SECRET is used as fallback.
Use the docker-compose.ymlβ in this repository:
docker compose up -d
With optional Redis cache:
docker compose --profile redis up -d
When using the Redis profile, set REDIS_ENABLED=true and REDIS_URL=redis://redis:6379 in .env.docker.
For MSSQL, point DB_HOST to your SQL Server instance instead of the bundled MySQL service.
docker run -d \
--name myelin \
-p 3000:3000 \
--env-file .env.docker \
tiag0ss/myelin:latest
http://localhost:3000 in your browser| Variable | Required | Default | Description |
|---|---|---|---|
DB_PROVIDER | No | mysql | Database provider (mysql or mssql) |
DB_HOST | Yes | mysql (Docker) | Database host |
DB_USER | Yes | appuser | Database user |
DB_PASSWORD | Yes | β | Database password |
DB_NAME | No | projectmanagement | Database name |
DB_CONNECTION_LIMIT | No | 50 | Connection pool size |
DB_PORT | No | 3306 / 1433 | Database port |
DB_ENCRYPT | No | false | MSSQL TLS encrypt flag |
DB_TRUST_SERVER_CERT | No | true | MSSQL trust server certificate |
JWT_SECRET | Yes | β | Secret key for JWT tokens |
ENCRYPTION_KEY | No | β | 64-char hex key for encrypted settings (falls back to JWT_SECRET) |
ALLOWED_ORIGINS | No | β | CORS allowed origins (comma-separated) |
NEXT_PUBLIC_API_URL | No | http://localhost:3000 | Public API URL (browser) |
API_URL | No | http://localhost:3000 | Internal API URL |
LOG_LEVEL | No | warn | Log level (debug / info / warn / error) |
PORT | No | 3000 | Application port |
NODE_ENV | No | development | Set to production in production |
DEMO | No | false | When true, enables demo-mode UI restrictions |
REDIS_ENABLED | No | false | Enable Redis read cache |
REDIS_URL | No | redis://localhost:6379 | Redis connection URL |
REDIS_KEY_PREFIX | No | pm: | Key prefix for all cache entries |
REDIS_DEFAULT_TTL_SECONDS | No | 300 | Safety TTL for cached entities (invalidated on writes) |
ALLOWED_SSO_REDIRECTS | No | β | Comma-separated SSO callback URLs (e.g. Synapse http://localhost:3010/api/auth/sso/callback) |
SSO_CLIENT_ID / SSO_CLIENT_SECRET | No | β | SSO client credentials for companions such as Synapseβ |
| Port | Description |
|---|---|
3000 | Application (frontend + API) |
3010 | Synapseβ companion (optional, separate process) |
6379 | Redis (optional, Docker redis profile only) |
3306 | MySQL (Docker Compose default) |
| Path | Description |
|---|---|
/app/logs | Application log files |
/app/uploads | File attachments (Tasks, Tickets, Projects, Organizations, Customers, Memos) |
Note: Rich text editor images are stored as base64 in the database, not in the uploads folder.
GET http://localhost:3000/health
Example response:
{
"status": "healthy",
"timestamp": "2026-07-02T22:00:00.000Z",
"uptime": 3600,
"database": "connected",
"redis": "disabled"
}
redis is connected, disabled, or error. Redis failures do not fail the health check β the app falls back to the database.
If you see stale lists after a create/update with Redis enabled, confirm invalidation is wired on that write route; as a fallback, restart the API or flush Redis keys matching your REDIS_KEY_PREFIX (default pm:*).
Single container serves Next.js frontend and Express API. MySQL or MSSQL is the system of record. Redis is optional.
+----------------------------------+
| myelin:latest |
| |
| +-----------+ +------------+ |
| | Next.js | | Express | |
| | Frontend | | API | |
| +-----------+ +------------+ |
| Port 3000 |
+----------------------------------+
| |
| +-----> Redis (optional)
|
+------+------+
| MySQL / MSSQL |
+---------------+
node:20-alpinenodejs, UID 1001)node dist/server/index.jsREDIS_ENABLED=true, reads are cached with invalidate-on-write; writes always go to the database firstdocker compose pull
docker compose up -d
docker exec myelin-mysql \
mysqldump -u root -p"$DB_PASSWORD" projectmanagement > backup.sql
docker exec -i myelin-mysql \
mysql -u root -p"$DB_PASSWORD" projectmanagement < backup.sql
For MSSQL, use your standard backup/restore tools (BACKUP DATABASE / RESTORE DATABASE).
Comprehensive testing scenarios are in TESTING_SCENARIOS.mdβ :
pnpm test
pnpm run test:coverage
pnpm run test:integration
MIT
Content type
Image
Digest
sha256:0838c10b8β¦
Size
677.1 MB
Last updated
7 days ago
docker pull tiag0ss/myelin