Adds taiga app.

This commit is contained in:
2026-06-19 21:33:26 +00:00
parent 1d57e26dae
commit a2852dab0a
22 changed files with 1015 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
# Taiga
Taiga is an open-source project management platform supporting Scrum, Kanban, and issue tracking.
## Dependencies
- **PostgreSQL** - Database for storing projects and tasks
## Configuration
Key settings in `config.yaml`:
- **domain** - Where Taiga will be accessible
- **mediaStorage** - Persistent volume for uploaded files (default: `2Gi`)
- **staticStorage** - Persistent volume for static assets (default: `2Gi`)
- **smtp** - SMTP settings for email notifications (configured manually in config)
## First-Time Setup
1. Add and deploy the app:
```bash
wild app add taiga
wild app deploy taiga
```
2. Visit the app URL and click **Register** to create your account.
3. The first user to register is granted admin access automatically.
4. Create projects and invite team members from the dashboard.
## Notes
- Taiga bundles its own RabbitMQ instance for async event processing
- SMTP must be configured in `config.yaml` for email notifications (invitations, mentions) to work — the `smtp` section is intentionally left blank by default and must be filled in
- The `secretKey` and `rabbitmqPassword` are auto-generated in `secrets.yaml`

View File

@@ -0,0 +1,82 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: taiga-gateway-config
namespace: {{ .namespace }}
data:
taiga.conf: |
server {
listen 80 default_server;
client_max_body_size 100M;
charset utf-8;
# Frontend
location / {
proxy_pass http://taiga-front/;
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
}
# API
location /api/ {
proxy_pass http://taiga-back:8000/api/;
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
}
# Admin
location /admin/ {
proxy_pass http://taiga-back:8000/admin/;
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
}
# Static files served directly from shared volume
location /static/ {
alias /taiga/static/;
}
# Protected media (internal only)
location /_protected/ {
internal;
alias /taiga/media/;
add_header Content-disposition "attachment";
}
# Unprotected exports
location /media/exports/ {
alias /taiga/media/exports/;
add_header Content-disposition "attachment";
}
# Media proxy (protected handler)
location /media/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://taiga-protected:8003/;
proxy_redirect off;
}
# WebSocket events
location /events {
proxy_pass http://taiga-events:8888/events;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_connect_timeout 7d;
proxy_send_timeout 7d;
proxy_read_timeout 7d;
}
}

View File

@@ -0,0 +1,59 @@
apiVersion: batch/v1
kind: Job
metadata:
name: taiga-db-init
labels:
component: db-init
spec:
template:
metadata:
labels:
component: db-init
spec:
restartPolicy: OnFailure
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
seccompProfile:
type: RuntimeDefault
containers:
- name: postgres-init
image: postgres:15
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
env:
- name: PGHOST
value: {{ .db.host }}
- name: PGUSER
value: postgres
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: taiga-secrets
key: postgres.password
- name: DB_NAME
value: {{ .db.name }}
- name: DB_USER
value: {{ .db.user }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: taiga-secrets
key: dbPassword
command:
- /bin/bash
- -c
- |
set -e
until pg_isready; do sleep 2; done
psql -c "CREATE DATABASE ${DB_NAME};" || echo "Database already exists"
psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" || echo "User already exists"
psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';"
psql -c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};"
psql -d ${DB_NAME} -c "GRANT ALL ON SCHEMA public TO ${DB_USER};"
echo "Done"

View File

@@ -0,0 +1,104 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: taiga-async
namespace: {{ .namespace }}
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: async
template:
metadata:
labels:
component: async
spec:
securityContext:
runAsNonRoot: false
runAsUser: 0
runAsGroup: 0
seccompProfile:
type: RuntimeDefault
containers:
- name: taiga-async
image: taigaio/taiga-back:6.10.1
command:
- /taiga-back/docker/async_entrypoint.sh
env:
- name: POSTGRES_DB
value: {{ .db.name }}
- name: POSTGRES_USER
value: {{ .db.user }}
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: taiga-secrets
key: dbPassword
- name: POSTGRES_HOST
value: {{ .db.host }}
- name: POSTGRES_PORT
value: "{{ .db.port }}"
- name: TAIGA_SECRET_KEY
valueFrom:
secretKeyRef:
name: taiga-secrets
key: secretKey
- name: TAIGA_SITES_SCHEME
value: https
- name: TAIGA_SITES_DOMAIN
value: {{ .domain }}
- name: TAIGA_SUBPATH
value: ""
- name: RABBITMQ_USER
value: taiga
- name: RABBITMQ_PASS
valueFrom:
secretKeyRef:
name: taiga-secrets
key: rabbitmqPassword
- name: RABBITMQ_VHOST
value: taiga
- name: RABBITMQ_HOST
value: taiga-rabbitmq
- name: TAIGA_ASYNC_RABBITMQ_HOST
value: taiga-rabbitmq
- name: EMAIL_BACKEND
value: django.core.mail.backends.smtp.EmailBackend
- name: DEFAULT_FROM_EMAIL
value: {{ .smtp.from }}
- name: EMAIL_HOST
value: {{ .smtp.host }}
- name: EMAIL_PORT
value: "{{ .smtp.port }}"
- name: EMAIL_HOST_USER
value: {{ .smtp.user }}
- name: EMAIL_HOST_PASSWORD
valueFrom:
secretKeyRef:
name: taiga-secrets
key: smtpPassword
- name: EMAIL_USE_TLS
value: "{{ .smtp.tls }}"
- name: EMAIL_USE_SSL
value: "{{ .smtp.ssl }}"
- name: ENABLE_TELEMETRY
value: "False"
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 128Mi
volumeMounts:
- name: taiga-media
mountPath: /taiga-back/media
securityContext:
readOnlyRootFilesystem: false
volumes:
- name: taiga-media
persistentVolumeClaim:
claimName: taiga-media
restartPolicy: Always

View File

@@ -0,0 +1,176 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: taiga-back
namespace: {{ .namespace }}
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: back
template:
metadata:
labels:
component: back
spec:
securityContext:
runAsNonRoot: false
runAsUser: 0
runAsGroup: 0
seccompProfile:
type: RuntimeDefault
initContainers:
- name: collectstatic
image: taigaio/taiga-back:6.10.1
command:
- python
- manage.py
- collectstatic
- --noinput
env:
- name: POSTGRES_DB
value: {{ .db.name }}
- name: POSTGRES_USER
value: {{ .db.user }}
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: taiga-secrets
key: dbPassword
- name: POSTGRES_HOST
value: {{ .db.host }}
- name: POSTGRES_PORT
value: "{{ .db.port }}"
- name: TAIGA_SECRET_KEY
valueFrom:
secretKeyRef:
name: taiga-secrets
key: secretKey
- name: TAIGA_SITES_SCHEME
value: https
- name: TAIGA_SITES_DOMAIN
value: {{ .domain }}
- name: TAIGA_SUBPATH
value: ""
- name: RABBITMQ_USER
value: taiga
- name: RABBITMQ_PASS
valueFrom:
secretKeyRef:
name: taiga-secrets
key: rabbitmqPassword
- name: RABBITMQ_VHOST
value: taiga
- name: RABBITMQ_HOST
value: taiga-rabbitmq
securityContext:
readOnlyRootFilesystem: false
volumeMounts:
- name: taiga-static
mountPath: /taiga-back/static
containers:
- name: taiga-back
image: taigaio/taiga-back:6.10.1
ports:
- name: http
containerPort: 8000
protocol: TCP
env:
- name: POSTGRES_DB
value: {{ .db.name }}
- name: POSTGRES_USER
value: {{ .db.user }}
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: taiga-secrets
key: dbPassword
- name: POSTGRES_HOST
value: {{ .db.host }}
- name: POSTGRES_PORT
value: "{{ .db.port }}"
- name: TAIGA_SECRET_KEY
valueFrom:
secretKeyRef:
name: taiga-secrets
key: secretKey
- name: TAIGA_SITES_SCHEME
value: https
- name: TAIGA_SITES_DOMAIN
value: {{ .domain }}
- name: TAIGA_SUBPATH
value: ""
- name: RABBITMQ_USER
value: taiga
- name: RABBITMQ_PASS
valueFrom:
secretKeyRef:
name: taiga-secrets
key: rabbitmqPassword
- name: RABBITMQ_VHOST
value: taiga
- name: RABBITMQ_HOST
value: taiga-rabbitmq
- name: TAIGA_EVENTS_RABBITMQ_HOST
value: taiga-rabbitmq
- name: EMAIL_BACKEND
value: django.core.mail.backends.smtp.EmailBackend
- name: DEFAULT_FROM_EMAIL
value: {{ .smtp.from }}
- name: EMAIL_HOST
value: {{ .smtp.host }}
- name: EMAIL_PORT
value: "{{ .smtp.port }}"
- name: EMAIL_HOST_USER
value: {{ .smtp.user }}
- name: EMAIL_HOST_PASSWORD
valueFrom:
secretKeyRef:
name: taiga-secrets
key: smtpPassword
- name: EMAIL_USE_TLS
value: "{{ .smtp.tls }}"
- name: EMAIL_USE_SSL
value: "{{ .smtp.ssl }}"
- name: ENABLE_TELEMETRY
value: "False"
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 50m
memory: 256Mi
volumeMounts:
- name: taiga-media
mountPath: /taiga-back/media
- name: taiga-static
mountPath: /taiga-back/static
livenessProbe:
httpGet:
path: /api/v1/
port: 8000
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 5
readinessProbe:
httpGet:
path: /api/v1/
port: 8000
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
securityContext:
readOnlyRootFilesystem: false
volumes:
- name: taiga-media
persistentVolumeClaim:
claimName: taiga-media
- name: taiga-static
persistentVolumeClaim:
claimName: taiga-static
restartPolicy: Always

View File

@@ -0,0 +1,69 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: taiga-events
namespace: {{ .namespace }}
spec:
replicas: 1
selector:
matchLabels:
component: events
template:
metadata:
labels:
component: events
spec:
securityContext:
runAsNonRoot: false
runAsUser: 0
runAsGroup: 0
seccompProfile:
type: RuntimeDefault
containers:
- name: taiga-events
image: taigaio/taiga-events:6.10.0
ports:
- name: ws
containerPort: 8888
protocol: TCP
env:
- name: RABBITMQ_USER
value: taiga
- name: RABBITMQ_PASS
valueFrom:
secretKeyRef:
name: taiga-secrets
key: rabbitmqPassword
- name: RABBITMQ_VHOST
value: taiga
- name: RABBITMQ_HOST
value: taiga-rabbitmq
- name: TAIGA_SECRET_KEY
valueFrom:
secretKeyRef:
name: taiga-secrets
key: secretKey
resources:
limits:
cpu: 250m
memory: 256Mi
requests:
cpu: 50m
memory: 64Mi
livenessProbe:
tcpSocket:
port: 8888
initialDelaySeconds: 20
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 8888
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
securityContext:
readOnlyRootFilesystem: false
restartPolicy: Always

View File

@@ -0,0 +1,61 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: taiga-front
namespace: {{ .namespace }}
spec:
replicas: 1
selector:
matchLabels:
component: front
template:
metadata:
labels:
component: front
spec:
securityContext:
runAsNonRoot: false
runAsUser: 0
runAsGroup: 0
seccompProfile:
type: RuntimeDefault
containers:
- name: taiga-front
image: taigaio/taiga-front:6.10.3
ports:
- name: http
containerPort: 80
protocol: TCP
env:
- name: TAIGA_URL
value: https://{{ .domain }}
- name: TAIGA_WEBSOCKETS_URL
value: wss://{{ .domain }}
- name: TAIGA_SUBPATH
value: ""
resources:
limits:
cpu: 250m
memory: 256Mi
requests:
cpu: 50m
memory: 64Mi
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 15
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
securityContext:
readOnlyRootFilesystem: false
restartPolicy: Always

View File

@@ -0,0 +1,75 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: taiga-gateway
namespace: {{ .namespace }}
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: gateway
template:
metadata:
labels:
component: gateway
spec:
securityContext:
runAsNonRoot: false
runAsUser: 0
runAsGroup: 0
seccompProfile:
type: RuntimeDefault
containers:
- name: taiga-gateway
image: nginx:1.27-alpine
ports:
- name: http
containerPort: 80
protocol: TCP
resources:
limits:
cpu: 250m
memory: 128Mi
requests:
cpu: 50m
memory: 32Mi
volumeMounts:
- name: gateway-config
mountPath: /etc/nginx/conf.d
- name: taiga-static
mountPath: /taiga/static
readOnly: true
- name: taiga-media
mountPath: /taiga/media
readOnly: true
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
securityContext:
readOnlyRootFilesystem: false
volumes:
- name: gateway-config
configMap:
name: taiga-gateway-config
- name: taiga-static
persistentVolumeClaim:
claimName: taiga-static
- name: taiga-media
persistentVolumeClaim:
claimName: taiga-media
restartPolicy: Always

View File

@@ -0,0 +1,71 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: taiga-protected
namespace: {{ .namespace }}
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: protected
template:
metadata:
labels:
component: protected
spec:
securityContext:
runAsNonRoot: false
runAsUser: 0
runAsGroup: 0
seccompProfile:
type: RuntimeDefault
containers:
- name: taiga-protected
image: taigaio/taiga-protected:6.10.1
ports:
- name: http
containerPort: 8003
protocol: TCP
env:
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: taiga-secrets
key: secretKey
- name: MAX_AGE
value: "360"
resources:
limits:
cpu: 250m
memory: 256Mi
requests:
cpu: 50m
memory: 64Mi
volumeMounts:
- name: taiga-media
mountPath: /taiga/media
livenessProbe:
httpGet:
path: /
port: 8003
initialDelaySeconds: 15
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 8003
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
securityContext:
readOnlyRootFilesystem: false
volumes:
- name: taiga-media
persistentVolumeClaim:
claimName: taiga-media
restartPolicy: Always

View File

@@ -0,0 +1,82 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: taiga-rabbitmq
namespace: {{ .namespace }}
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: rabbitmq
template:
metadata:
labels:
component: rabbitmq
spec:
securityContext:
runAsNonRoot: false
runAsUser: 0
runAsGroup: 0
seccompProfile:
type: RuntimeDefault
containers:
- name: rabbitmq
image: rabbitmq:3.12-alpine
ports:
- name: amqp
containerPort: 5672
protocol: TCP
env:
- name: RABBITMQ_DEFAULT_USER
value: taiga
- name: RABBITMQ_DEFAULT_PASS
valueFrom:
secretKeyRef:
name: taiga-secrets
key: rabbitmqPassword
- name: RABBITMQ_DEFAULT_VHOST
value: taiga
- name: RABBITMQ_ERLANG_COOKIE
valueFrom:
secretKeyRef:
name: taiga-secrets
key: rabbitmqErlangCookie
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 128Mi
volumeMounts:
- name: taiga-rabbitmq-data
mountPath: /var/lib/rabbitmq
livenessProbe:
exec:
command:
- rabbitmq-diagnostics
- -q
- ping
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
exec:
command:
- rabbitmq-diagnostics
- -q
- check_port_connectivity
initialDelaySeconds: 20
periodSeconds: 15
timeoutSeconds: 10
failureThreshold: 3
securityContext:
readOnlyRootFilesystem: false
volumes:
- name: taiga-rabbitmq-data
persistentVolumeClaim:
claimName: taiga-rabbitmq-data
restartPolicy: Always

View File

@@ -0,0 +1,26 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: taiga
namespace: {{ .namespace }}
annotations:
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
external-dns.alpha.kubernetes.io/ttl: "60"
spec:
ingressClassName: traefik
rules:
- host: {{ .domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: taiga-gateway
port:
number: 80
tls:
- hosts:
- {{ .domain }}
secretName: {{ .tlsSecretName }}

View File

@@ -0,0 +1,28 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: {{ .namespace }}
labels:
- includeSelectors: true
pairs:
app: taiga
managedBy: kustomize
partOf: wild-cloud
resources:
- namespace.yaml
- configmap-gateway.yaml
- deployment-back.yaml
- deployment-async.yaml
- deployment-front.yaml
- deployment-events.yaml
- deployment-protected.yaml
- deployment-gateway.yaml
- deployment-rabbitmq.yaml
- service-back.yaml
- service-front.yaml
- service-events.yaml
- service-protected.yaml
- service-gateway.yaml
- service-rabbitmq.yaml
- ingress.yaml
- pvc.yaml
- db-init-job.yaml

View File

@@ -0,0 +1,30 @@
version: 6.10.3-2
requires:
- name: postgres
defaultConfig:
namespace: taiga
externalDnsDomain: "{{ .cloud.domain }}"
domain: taiga.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls
mediaStorage: 2Gi
staticStorage: 2Gi
db:
host: "{{ .apps.postgres.host }}"
port: "{{ .apps.postgres.port }}"
name: taiga
user: taiga
smtp:
host: ""
port: "587"
user: ""
from: taiga@{{ .cloud.domain }}
tls: "True"
ssl: "False"
defaultSecrets:
- key: secretKey
- key: dbPassword
- key: rabbitmqPassword
- key: rabbitmqErlangCookie
- key: smtpPassword
requiredSecrets:
- postgres.password

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: {{ .namespace }}

35
taiga/versions/6/pvc.yaml Normal file
View File

@@ -0,0 +1,35 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: taiga-media
namespace: {{ .namespace }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .mediaStorage }}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: taiga-static
namespace: {{ .namespace }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .staticStorage }}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: taiga-rabbitmq-data
namespace: {{ .namespace }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: taiga-back
namespace: {{ .namespace }}
spec:
selector:
component: back
ports:
- name: http
port: 8000
targetPort: 8000

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: taiga-events
namespace: {{ .namespace }}
spec:
selector:
component: events
ports:
- name: ws
port: 8888
targetPort: 8888

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: taiga-front
namespace: {{ .namespace }}
spec:
selector:
component: front
ports:
- name: http
port: 80
targetPort: 80

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: taiga-gateway
namespace: {{ .namespace }}
spec:
selector:
component: gateway
ports:
- name: http
port: 80
targetPort: 80

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: taiga-protected
namespace: {{ .namespace }}
spec:
selector:
component: protected
ports:
- name: http
port: 8003
targetPort: 8003

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: taiga-rabbitmq
namespace: {{ .namespace }}
spec:
selector:
component: rabbitmq
ports:
- name: amqp
port: 5672
targetPort: 5672