Exploring Open Source Project Management Systems - Vikunja

I have been looking to implement a project management solution that works effectively at both the individual and team level, and I reviewed several open-source platforms to assess their suitability. My goal was to find a tool that balances functionality with ease of use, even for non-technical or non-PM users.

Evaluation Criteria:

  1. User-Friendly Interface: The platform should be intuitive and easy to use for team members without technical backgrounds. The tool must be flexible enough to manage projects that are not related to software development.
  2. Gantt Chart Support: A visual way to track project timelines is essential.
  3. Notifications and Alerts: The platform should be able to send automated reminders for due dates and notifications for mentions.
  4. Multi-Team Management: The tool should support the coordination of multiple teams working on different projects.

Tools Evaluated:

  1. Wekan
    • Pros: Decent feature set.
    • Cons: Outdated UI and navigation options. While custom themes can address these issues, I expected better out-of-the-box usability.
  2. Redmine
    • Pros: Useful for software development tracking.
    • Cons: Severely outdated interface and navigation focused primarily on development workflows.
  3. Zentao
    • Pros: Clean layout.
    • Cons: Limited translation support, which makes it challenging to use for non-Chinese speakers. Heavily focused on software development.
  4. Focalboard
    • Pros: Modern design and easy to use.
    • Cons: Lacks collaboration options, making it difficult to manage team projects effectively. More importantly, the project is no longer developed.
  5. Planka
    • Pros: Amazing tool for personal projects or small-scale use.
    • Cons: Gets cluttered and inefficient when multiple people work on a larger project.
  6. OpenProject
    • Pros: Decent range of features.
    • Cons: Many key features are locked behind a paid tier, reducing its value as a free solution.
  7. Plane
    • Cons: Was unable to deploy due to poor documentation. Way too many docker containers are required for it to operate.
  8. Kanboard
    • Pros: Strong feature set.
    • Cons: Outdated interface and cumbersome navigation make it impractical for production use.
  9. Leantime
    • Pros: Offers a flexible project management setup.
    • Cons: Provides too many irrelevant options by default. The platform is largely marketed toward ADHD users, so this might be expected.
  10. Taiga
    • Cons: Could not deploy successfully despite multiple attempts using the official guide. Documentation needs improvement.
  11. Vikunja
    • Pros: Combines Kanban, Gantt charts, and basic collaboration options. Supports email notifications and has good project tracking capabilities.
    • Cons: Self-hosted version has limited user management options, which must be configured via the command line (relatively easy to automate, though).

I found Vikunja to be the best "middle-ground" solution. It offers a good balance of features—Kanban boards, Gantt charts, and email notifications—making it a suitable choice for both individuals and teams. The only drawback is that user management in the self-hosted version requires command-line interaction, but this can be streamlined through automation.

Focalboard:

version: '2'
services:
  focalboard:
    image: mattermost/focalboard:latest #the Docker image for Focalboard that we are using
    container_name: focalboard
    volumes:
      - focalboard:/data
    ports:
      - "3020:8000" #any port that you have free in your machine
    restart: always 

volumes:
  focalboard:

Kanboard:

services:
  kanboard:
    image: kanboard/kanboard:latest
    restart: always
    ports:
     - "8091:80"
     - "8092:443"
    volumes:
     - kanboard_data:/var/www/app/data
     - kanboard_plugins:/var/www/app/plugins
     - kanboard_ssl:/etc/nginx/ssl
  
volumes:
  kanboard_data:
    driver: local
  kanboard_plugins:
    driver: local
  kanboard_ssl:
    driver: local

Planka:

version: '3'

services:
  planka:
    image: ghcr.io/plankanban/planka:latest
    restart: always
    volumes:
      - user-avatars:/app/public/user-avatars
      - project-background-images:/app/public/project-background-images
      - attachments:/app/private/attachments
    ports:
      - 3010:1337
    environment:
      - BASE_URL=https://yourdomain.com
      - DATABASE_URL=postgresql://postgres@postgres/planka
      - SECRET_KEY=
      - TRUST_PROXY=1
      - TOKEN_EXPIRES_IN=365 # In days

      # related: https://github.com/knex/knex/issues/2354
      # As knex does not pass query parameters from the connection string we
      # have to use environment variables in order to pass the desired values, e.g.
      # - PGSSLMODE=<value>

      # Configure knex to accept SSL certificates
      # - KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE=false

      - [email protected] # Do not remove if you want to prevent this user from being edited/deleted
      - DEFAULT_ADMIN_PASSWORD=strongpassword
      - DEFAULT_ADMIN_NAME=Your Name
      - [email protected]

      # - ALLOW_ALL_TO_CREATE_PROJECTS=true

      # - OIDC_ISSUER=
      # - OIDC_CLIENT_ID=
      # - OIDC_CLIENT_SECRET=
      # - OIDC_ID_TOKEN_SIGNED_RESPONSE_ALG=
      # - OIDC_USERINFO_SIGNED_RESPONSE_ALG=
      # - OIDC_SCOPES=openid email profile
      # - OIDC_RESPONSE_MODE=fragment
      # - OIDC_USE_DEFAULT_RESPONSE_MODE=true
      # - OIDC_ADMIN_ROLES=admin
      # - OIDC_EMAIL_ATTRIBUTE=email
      # - OIDC_NAME_ATTRIBUTE=name
      # - OIDC_USERNAME_ATTRIBUTE=preferred_username
      # - OIDC_ROLES_ATTRIBUTE=groups
      # - OIDC_IGNORE_USERNAME=true
      # - OIDC_IGNORE_ROLES=true
      # - OIDC_ENFORCED=true

      # Email Notifications (https://nodemailer.com/smtp/)
      - SMTP_HOST=smtp.office365.com
      - SMTP_PORT=587
      - SMTP_NAME=IT
      - SMTP_SECURE=true
      - [email protected]
      - SMTP_PASSWORD=strongpassword
      - SMTP_FROM="Your Name" <[email protected]>

      # Optional fields: accessToken, events, excludedEvents
      # - |
      #   WEBHOOKS=[{
      #     "url": "http://localhost:3001",
      #     "accessToken": "notaccesstoken",
      #     "events": ["cardCreate", "cardUpdate", "cardDelete"],
      #     "excludedEvents": ["notificationCreate", "notificationUpdate"]
      #   }]

      # - SLACK_BOT_TOKEN=
      # - SLACK_CHANNEL_ID=
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - shared_network

  postgres:
    image: postgres:14-alpine
    restart: always
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=planka
      - POSTGRES_HOST_AUTH_METHOD=trust
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d planka"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - shared_network

volumes:
  user-avatars:
  project-background-images:
  attachments:
  db-data:


networks:
  shared_network:
    external: false

And finally, Vikunja:

services:
  vikunja:
    image: vikunja/vikunja
    environment:
      VIKUNJA_SERVICE_PUBLICURL: https://yourdomain.com
      VIKUNJA_DATABASE_HOST: vikunja-db
      VIKUNJA_DATABASE_PASSWORD: "strongpassword"
      VIKUNJA_DATABASE_TYPE: postgres
      VIKUNJA_DATABASE_USER: vikunja
      VIKUNJA_DATABASE_DATABASE: vikunja
      VIKUNJA_SERVICE_JWTSECRET: "strongpassword"
      VIKUNJA_SERVICE_ENABLETASKATTACHMENTS: 0
      VIKUNJA_SERVICE_ENABLEREGISTRATION: 0
      VIKUNJA_SERVICE_ENABLEEMAILREMINDERS: 1
      VIKUNJA_MAILER_ENABLED: true
      VIKUNJA_MAILER_AUTHTYPE: login
      VIKUNJA_MAILER_HOST: smtp.office365.com
      VIKUNJA_MAILER_PORT: 587
      VIKUNJA_MAILER_FROMEMAIL: [email protected]
      VIKUNJA_MAILER_USERNAME: [email protected]
      VIKUNJA_MAILER_PASSWORD: strongpassword
      VIKUNJA_DEFAULTSETTINGS_EMAIL_REMINDERS_ENABLED: true
      VIKUNJA_DEFAULTSETTINGS_DISCOVERABLE_BY_NAME: true
      VIKUNJA_DEFAULTSETTINGS_DISCOVERABLE_BY_EMAIL: true
      VIKUNJA_DEFAULTSETTINGS_OVERDUE_TASKS_REMINDERS_ENABLED: true
      VIKUNJA_DEFAULTSETTINGS_WEEK_START: 1
      VIKUNJA_SERVICE_CUSTOMLOGOURL: https://domain.com/logo.png
    ports:
      - 3456:3456
    volumes:
      - app:/app/vikunja/files
    depends_on:
      vikunja-db:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - shared_network

  vikunja-db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: "strongpassword"
      POSTGRES_USER: vikunja
    volumes:
      - vikunja-db:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -h localhost -U $$POSTGRES_USER"]
      interval: 2s
    networks:
      - shared_network

volumes:
  vikunja-db:
  app:

networks:
  shared_network:
    external: false

I do want to highlight that Vikunja does not have a management UI for user management (or any management at all). All system-wide changes are based on CLI commands. To simplify usage, I leveraged n8n automations to provision and deprovision user accounts easily.

User creation:

docker exec -i vikunja-vikunja-1 /app/vikunja/vikunja user create -u {{ $json.username }} -e {{ $json.email }} -p "{{ $json.password }}"

User deprovisioning is more complicated as users are assigned a system ID. This system ID must be extracted first in order to manipulate the account:

docker exec -i vikunja-vikunja-1 /app/vikunja/vikunja user list

This returns a list of users

let formattedOutput = [];

for (const item of $input.all()) {
    const rawData = item.json["User List Raw"] || "";

    const userPattern = /\|\s+(\d+)\s+\|\s+(\w+)\s+\|\s+([\w\.\-@]+)\s+\|\s+(\w+)\s+\|\s+([\d\-T:Z]+)\s+\|\s+([\d\-T:Z]+)\s+\|/g;
    let userTable = [];
    let userMatch;

    while ((userMatch = userPattern.exec(rawData)) !== null) {
        const [, id, username, email, status, created, updated] = userMatch;
        userTable.push({
            "ID": parseInt(id),
            "Username": username,
            "Email": email,
            "Status": status,
            "Created": created,
            "Updated": updated
        });
    }

    formattedOutput.push(...userTable);
}

return formattedOutput;

$usern

Transforming data from the STOUT

const inputData = items; 
const targetEmail = $node["Vikunja User Info1"].json.email;

const matchingUser = inputData.find(item => item.json.Email === targetEmail);

return [
  {
    json: {
      ID: matchingUser ? matchingUser.json.ID : "Email not found",
    },
  },
];

Finding the corresponding User ID

docker exec -i vikunja-vikunja-1 /app/vikunja/vikunja user change-status {{ $json.userID }} -d

Finally, deactivate the user based on their User ID