A self-hosted, open-source agile ticket tracker for small teams who want Kanban boards, a knowledge base, real-time collaboration, and a REST API — without the overhead of Jira or the cost of Linear.
Built with Laravel. Tickets is proudly powered by the Laravel framework — its expressive syntax, robust ecosystem, and first-class tooling make it the backbone of this application.
The fastest way to get running is Docker:
git clone https://github.com/velkymx/tickets.git
cd tickets
docker compose up -d --buildOpen http://localhost and log in with the default administrator account. See Installation for manual setup or Hosting for production deployment.
- Create, edit, clone, and batch-update tickets with full metadata (type, status, importance, project, milestone, assignee, due date, story points, estimates)
- Kanban board with drag-and-drop status changes (powered by SortableJS)
- Multi-filter list view with search, pagination, and per-page control
- CSV import for bulk ticket creation
- Ticket Pulse — real-time execution state (ON TRACK, AT RISK, BLOCKED, IDLE) with blocker surfacing, decision tracking, and open thread monitoring
- Threaded notes with reply support, pinning, hiding, and emoji reactions
- Signal types: message, decision, blocker, action, update (auto-generated changelog entries)
- Slash commands:
/decision,/blocker,/action,/assign,/status,/hours,/estimate,/close,/reopen,/pin,/update - @mention autocomplete with keyboard navigation
- Smart paste detection (auto-formats stack traces, JSON, and URLs)
- Markdown toolbar with live preview
- File attachments on notes and KB articles
- Article management with Markdown (EasyMDE editor), categories, and tags
- Version history with diff comparison and restore
- Article visibility: public, internal, restricted (with per-user permissions)
- Full-text search across articles, categories, and tags
- Quick-create categories and tags inline during article creation
- Soft deletes with admin trash recovery
- Live presence indicators showing who's viewing a ticket (with composing status)
- Watcher system with email + database notifications
- Notification batching — rapid updates to the same ticket are grouped into digest emails
- Notification bell with unread count and activity feed
- Cross-reference linking:
#123links to tickets,kb:sluglinks to KB articles
- Milestones with sprint reports, burndown charts, and progress tracking
- Releases with ticket association
- Projects with progress tracking and filtered views
- Theme support: Light (Simplex), Dark (Darkly), or Auto (OS preference)
- User profiles with Gravatar avatars
- REST API with token authentication
- AI agent integration via the API — see docs/crewai.md for an example using CrewAI
| List View | Kanban Board |
|---|---|
![]() |
![]() |
| Ticket Detail | Milestone Report |
|---|---|
![]() |
![]() |
For manual installation you need:
- PHP 8.2+
- Composer
- MariaDB 11.8+ (or MySQL 8.0+, PostgreSQL 12+, SQLite 3.35+)
- Node.js 24+ (for building frontend assets)
Using Docker? Everything is included — skip to Hosting.
git clone https://github.com/velkymx/tickets.git
cd tickets
composer install
npm installCreate an empty database for the application. For MariaDB/MySQL:
CREATE DATABASE tickets;cp .env.example .env
php artisan key:generateEdit .env and set your database credentials:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=tickets
DB_USERNAME=root
DB_PASSWORD=your_password
Also set APP_URL to your local development URL (e.g. http://localhost:8000).
php artisan migrate
php artisan storage:linkThis step is critical — it creates the default lookup tables (statuses, types, importance levels) and the administrator account.
php artisan db:seed --class=DefaultsSeeder
php artisan db:seed --class=UserSeederDefault users created by the seeder:
| User | Password |
|---|---|
| unassigned | (no password) |
| administrator | password123 |
Change the administrator password immediately after first login. The default password is public knowledge. Navigate to your profile and update it before exposing the application to any network.
npm run build
php artisan serveOpen http://localhost:8000 in your browser and log in with the administrator account.
php artisan testThe included docker-compose.yml provides a full production-ready stack: PHP-FPM 8.5, Nginx, MariaDB 11.8, and Redis. A queue worker runs as a separate container. No additional software is required on the host — just Docker.
docker compose up -d --buildThat's it. On first boot, the entrypoint will:
- Copy
.env.exampleto.env(if missing) and generate an app key - Run migrations
- Seed default data (statuses, types, importance levels, and the administrator account)
- Cache config, routes, views, and events
Open http://localhost and log in with the administrator account:
| User | Password |
|---|---|
| administrator | password123 |
Change the administrator password immediately after first login. The default password is public knowledge. Navigate to your profile and update it before exposing the application to any network.
To customize, create a .env file before starting. The docker-compose file overrides DB_HOST, DB_USERNAME, DB_PASSWORD, REDIS_HOST, QUEUE_CONNECTION, SESSION_DRIVER, and CACHE_STORE automatically for the containers, so you only need to set values that differ from the defaults.
The stack includes:
| Service | Role |
|---|---|
app |
PHP-FPM 8.5 — runs migrations and caches config on boot |
nginx |
Nginx — serves static files and proxies PHP to app |
db |
MariaDB 11.8 — persistent data volume |
redis |
Redis — cache, sessions, queues |
queue |
Queue worker — processes notification digest jobs |
For non-Docker deployments, follow the Laravel deployment guide for web server configuration. Here are the steps specific to Tickets:
1. Install dependencies and build assets
composer install --no-dev --optimize-autoloader
npm ci
npm run build2. Set .env for production
APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-domain.com
QUEUE_CONNECTION=database
Configure a real mail driver so notifications are delivered:
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=postmaster@your-domain.com
MAIL_PASSWORD=your_password
MAIL_FROM_ADDRESS="tickets@your-domain.com"
3. Run migrations, link storage, and cache
php artisan migrate --force
php artisan storage:link
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache4. Seed default data (first deploy only)
php artisan db:seed --class=DefaultsSeeder
php artisan db:seed --class=UserSeederChange the administrator password immediately after seeding. Do not leave the default password active on a production system.
5. Run a queue worker
Notification digests are dispatched as queued jobs. Run a persistent worker via Supervisor:
[program:tickets-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/tickets/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
numprocs=1
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/tickets-worker.logOr use systemd, or any process manager that keeps php artisan queue:work running.
All API endpoints require a Bearer token.
Base URL: /api/v1
Generate an API token from your profile page: click your avatar in the top-right, then click Generate API Token. Tokens can be revoked from the same page.
curl -H "Authorization: Bearer YOUR_TOKEN" https://your-domain.com/api/v1/healthcurl -H "Authorization: Bearer YOUR_TOKEN" https://your-domain.com/api/v1/health{ "status": "ok" }curl -H "Authorization: Bearer YOUR_TOKEN" https://your-domain.com/api/v1/lookupsReturns available statuses, types, importance levels, projects, milestones, releases, and users. Use the IDs from this response when creating or updating tickets.
curl -H "Authorization: Bearer YOUR_TOKEN" https://your-domain.com/api/v1/tickets| Parameter | Type | Description |
|---|---|---|
| status | integer | Filter by status ID |
| unassigned | boolean | Return unassigned tickets |
| pulse | boolean | Include pulse data |
curl -H "Authorization: Bearer YOUR_TOKEN" https://your-domain.com/api/v1/tickets/1Returns full ticket details including notes with reactions, replies, attachments, and mentions.
curl -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subject": "Fix login bug", "description": "Users cannot log in with SSO", "project_id": 1}' \
https://your-domain.com/api/v1/tickets| Field | Type | Required |
|---|---|---|
| subject | string | Yes |
| description | string | No |
| type_id | integer | No |
| importance_id | integer | No |
| project_id | integer | No |
| milestone_id | integer | No |
| due_at | date | No |
| estimate | numeric | No |
| storypoints | integer | No |
curl -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body": "Investigating the root cause", "status_id": 2}' \
https://your-domain.com/api/v1/tickets/1/note| Field | Type | Description |
|---|---|---|
| body | string | Note content (supports slash commands) |
| status_id | integer | Update ticket status |
| hours | numeric | Log time |
| claim | boolean | Assign ticket to yourself |
curl -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"resolution_message": "Fixed in commit abc123"}' \
https://your-domain.com/api/v1/tickets/1/notes/5/resolve| Field | Type | Required |
|---|---|---|
| resolution_message | string | Yes |
curl -H "Authorization: Bearer YOUR_TOKEN" https://your-domain.com/api/v1/tickets/1/pulseReturns execution state, latest blocker, next action, latest decision, and open threads.
Bulk-create tickets via CSV. Before importing, create at least one project in the UI (Projects > New Project). The Project Name and Assigned To User Name columns must match existing values exactly.
Navigate to Tickets > Import to upload your CSV file.
Your CSV must include these columns in order:
| Column | Description |
|---|---|
| Type Name | bug, enhancement, task, proposal |
| Subject | Ticket title |
| Details | Ticket description |
| Importance Name | trivial, minor, major, critical, blocker |
| Status Name | new, active, testing, ready to deploy, completed, waiting, reopened, duplicate, declined |
| Project Name | Must match an existing project |
| Assigned To User Name | Must match a user's full name |
Example with multiple rows:
Type Name,Subject,Details,Importance Name,Status Name,Project Name,Assigned To User Name
bug,"Fix profile picture upload","The image is getting stretched on upload.",major,new,"Frontend App","Alan Smith"
task,"Update API docs","Document the new /pulse endpoint.",minor,active,"Backend API","Jane Doe"
enhancement,"Dark mode toggle","Add a switch in settings to toggle dark mode.",trivial,new,"Frontend App","unassigned"Contributions are welcome. Please read the Contributing Guide and Code of Conduct before opening a pull request.
Found a security issue? See the Security Policy. Do not open a public issue.
Tickets is built on the shoulders of these open-source projects:
Framework & Server
- Laravel — PHP framework (MIT)
- Guzzle — HTTP client (MIT)
- HTML Purifier — XSS protection (LGPL)
Frontend
- Bootstrap — CSS framework (MIT)
- Bootswatch — Bootstrap themes (MIT)
- Alpine.js — lightweight JS framework (MIT)
- Chart.js — charting (MIT)
- EasyMDE — Markdown editor (MIT)
- Quill — rich text editor (BSD-3)
- SortableJS — drag-and-drop (MIT)
- Font Awesome — icons (MIT, Font Awesome Free License)
Build & Dev
Tickets is open-sourced software licensed under the MIT license.
Copyright Alan Bollinger. blog.ajb.bz



