--- title: Installation description: Install and deploy TaskView using Docker Compose. Step-by-step setup guide for a self-hosted task management server with PostgreSQL, Node.js API, and Vue web app. Deploy on any server in 5 minutes. navigation: icon: i-lucide-download --- TaskView runs as a set of Docker containers - a database, an API server, a web app, and a one-time migration runner. The whole setup takes about 5 minutes. ## Prerequisites - A server or local machine with [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) installed - Ports `8888` (web) and `1725` (API) available - you can change these in the compose file ## Step 1: Create a project directory ```bash mkdir taskview && cd taskview ``` ## Step 2: Create environment files You need two env files - one for PostgreSQL, one for the TaskView API. **`.env.postgresql`** - database credentials: ```env POSTGRES_USER=taskview_db_user POSTGRES_PASSWORD=your_secure_password POSTGRES_DB=taskviewdb ``` **`.env.taskview`** - application config (**example, do not forget add your data**): ```env DB_HOST="db" DB_USER="taskview_db_user" DB_PASSWORD="your_secure_password" DB_NAME="taskviewdb" DB_PORT=5432 # Postgres connections per API worker (this is the default) DB_POOL_MAX=20 APP_PORT=1401 # API worker processes (this is the default). Accepts a number or "max" (one worker per CPU core). # Keep PM2_INSTANCES x DB_POOL_MAX below the Postgres max_connections limit (default 100). PM2_INSTANCES=2 JWT_ALG="HS256" JWT_SIGN="secret" ACCESS_LIFE_TIME="3d" REFRESH_LIFE_TIME="9d" SMTP_HOST=smtp SMTP_PORT=587 SMTP_USERNAME= SMTP_PASSWORD= SMTP_ENCRYPTION=tls SMTP_FROM_NAME=TaskView SMTP_FROM_EMAIL= # Your domain APP_URL="https://app.taskview.tech" GOOGLE_CLIENT_ID="" GOOGLE_CLIENT_SECRET="" #You domain GOOGLE_CALLBACK_URL="https://api.taskview.tech/module/auth/provider/google/callback" GITHUB_CLIENT_ID="" GITHUB_CLIENT_SECRET="" GITHUB_CALLBACK_URL="https://api.taskview.tech/module/auth/provider/github/callback" APPLE_CLIENT_ID="" APPLE_TEAM_ID="" APPLE_KEY_ID="" APPLE_KEY_LOCATION="/usr/src/app/AuthKey.p8" # Your domain APPLE_CALLBACK_URL="https://api.taskview.tech/module/auth/provider/apple/callback" #integrations GITHUB_INTEGRATION_CLIENT_ID= GITHUB_INTEGRATION_CLIENT_SECRET= GITHUB_INTEGRATION_CALLBACK_URL=https://api.taskview.tech/module/integrations/oauth/github/callback GITLAB_INTEGRATION_CLIENT_ID= GITLAB_INTEGRATION_CLIENT_SECRET= GITLAB_INTEGRATION_CALLBACK_URL=https://api.taskview.tech/module/integrations/oauth/github/callback ENCRYPTION_KEY= #!!! ADD YOUR DOMAIN SEPARATED BY "," CORS_ALLOWED_ORIGINS="http://localhost:5173,http://127.0.0.1:5173,http://localhost:3000,http://localhost:8888,http://127.0.0.1:3000,http://127.0.0.1:8888" ``` ::callout{icon="i-lucide-shield" color="warning"} Replace `your_secure_password` and `JWT_SIGN` with real secrets. Never use the example values in production. :: ## Step 3: Create docker-compose.yml ```yaml networks: backend: services: db: image: postgres:17 restart: unless-stopped env_file: - ./.env.postgresql volumes: - pgdata:/var/lib/postgresql/data ports: - "5433:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U taskview_db_user -d taskviewdb"] interval: 5s timeout: 5s retries: 5 networks: [backend] migration: image: gimanhead/taskview-ce-db-migration:latest restart: "no" depends_on: db: condition: service_healthy env_file: - ./.env.taskview networks: [backend] taskview-api-server: image: gimanhead/taskview-ce-api-server:latest restart: "unless-stopped" sysctls: - net.ipv6.conf.all.disable_ipv6=1 - net.ipv6.conf.default.disable_ipv6=1 ports: - "1725:1401" depends_on: db: condition: service_healthy migration: condition: service_completed_successfully env_file: - ./.env.taskview volumes: - ./logs:/usr/src/app/logs #- /local/AuthKey.p8:/usr/src/app/AuthKey.p8 networks: [backend] taskview-webapp: image: gimanhead/taskview-ce-webapp:latest restart: unless-stopped environment: # The web app will always use this API server and hide the server selector on the login page. # Remove this variable if you want to pick the API server manually on the login page. TASKVIEW_API_URL: "http://localhost:1725" ports: - "8888:80" # Enable for realtime notification read https://taskview.tech/docs/configuration/environment-variables#centrifugo-configuration-file # centrifugo: # image: centrifugo/centrifugo:v6 # restart: unless-stopped # command: centrifugo -c config.json # ports: # - "8000:8000" # volumes: # - /path-to/centrifugo/config.json:/centrifugo/config.json # networks: [backend] volumes: pgdata: ``` ## Step 4: Start everything ```bash docker compose up -d ``` Docker will pull the images, start the database, run migrations, and launch the API and web app. ## Step 5: Open TaskView Go to [http://localhost:8888](http://localhost:8888) in your browser. You'll see the login screen. ### Configure the API server The web app (port 8888) serves the frontend, while the API server (port 1725) handles authentication, projects, tasks, and all backend operations. If you set `TASKVIEW_API_URL` on the `taskview-webapp` service (as in the compose file above), there is nothing to configure — the web app already knows where the API is, and the server selector is hidden from the login page. Without `TASKVIEW_API_URL`, click the **server settings** section on the login page and add the API server URL manually: ``` http://localhost:1725 ``` ### Log in with the default user The database migration creates a default user so you can log in right away: - **Login:** `user` - **Password:** `user1!#Q` Use these credentials to verify that everything is working - check that the UI loads, you can create a project, add tasks, etc. ::callout{icon="i-lucide-alert-triangle" color="error"} **Important:** The default credentials are publicly known — anyone who has read this page can sign in to a fresh installation. Claim the account right after the first login. :: ### Claim the default account Make the default account your own — no SMTP or database access needed: 1. Log in with the default credentials 2. Open **Account settings** — the highlighted **Login and email** card is shown at the top (it is visible only to the default user) 3. Set your own login, email and a strong password, confirm with the current password (`user1!#Q`), and click **Save and sign out** 4. Sign in again with your new login and password ![The Login and email card in Account settings for claiming the default account](/taskview/change-def-account.png) Your organizations, projects and permissions are preserved. Once the email is changed, the card disappears and the claim endpoint is disabled. ::callout{icon="i-lucide-mail" color="info"} Changing the password later requires a confirmation code sent by email. If your installation has no SMTP, set `PASSWORD_CHANGE_CONFIRMATION="password"` in `.env.taskview` so password changes are confirmed with the current password instead. See [Environment Variables](/docs/configuration/environment-variables#authentication). :: ## Updating To update TaskView to a new version: ```bash docker compose pull docker compose up -d ``` The migration container will automatically apply any new database changes on startup. ## Production tips - **Use a reverse proxy** (Nginx, Caddy, Traefik) to terminate SSL and serve everything over HTTPS - **Update `APP_URL`** in `.env.taskview` and `TASKVIEW_API_URL` on the webapp service to match your production domains - **Trim the login page** — set `AUTH_LOGIN_METHODS` in `.env.taskview` to offer only the sign-in methods you actually use (e.g. `AUTH_LOGIN_METHODS="password"`). Google/GitHub/Apple buttons are shown only when the provider is configured. - **Close the instance** — set `ALLOW_PUBLIC_REGISTRATION="false"` so strangers can't create accounts: only emails invited to an organization or project (and users coming through your SSO provider) can sign in and get an account on first login. See [how it works](/docs/configuration/environment-variables#closing-an-instance-how-allow_public_registrationfalse-works). - **Scale API workers deliberately** — the API runs `PM2_INSTANCES` worker processes (default `2`), and each worker opens its own pool of up to `DB_POOL_MAX` Postgres connections (default `20`). Before raising either value (or using `PM2_INSTANCES=max`), make sure `workers × DB_POOL_MAX` stays below your Postgres `max_connections` (default `100`) — otherwise the API fails with *"sorry, too many clients already"*. The API logs a warning on startup when the budget looks too high. - **Back up the database** - the `pgdata` volume contains all your data - **Set `restart: unless-stopped`** on all services so they survive server reboots - **SMTP setup** - add SMTP variables to `.env.taskview` if you want email features (password recovery, invitations). See [Configuration](/docs/configuration/environment-variables) for details. ## What's next - [Create your first project](/docs/features/projects-and-lists) - set up a project with lists and tasks - [Invite your team](/docs/collaboration/members) - add members and assign roles - [Connect GitHub or GitLab](/docs/integrations/setup) - sync issues as tasks