# Docker Windshift provides official Docker images built as minimal scratch containers. The multi-stage build produces a small image containing only the compiled binary, CA certificates, and timezone data. ## Quick Start ```bash docker run -d \ --name windshift \ -p 8080:8080 \ --tmpfs /tmp:size=64M \ -v windshift-data:/data \ -e BASE_URL=http://localhost:8080 \ -e SSO_SECRET=$(openssl rand -hex 32) \ ghcr.io/windshiftapp/windshift:latest ``` > **Note:** This generates a random secret on each `docker run`. For production, generate a secret once and pass it explicitly so it persists across container restarts - see the Docker Compose examples below. ## Docker Compose The recommended way to run Windshift in production. Create a `docker-compose.yml`: ```yaml services: windshift: image: ghcr.io/windshiftapp/windshift:latest restart: unless-stopped ports: - "8080:8080" tmpfs: - /tmp:size=64M environment: - BASE_URL=https://windshift.example.com - SSO_SECRET=${SSO_SECRET} - DB_PATH=/data/windshift.db - ATTACHMENT_PATH=/data/attachments volumes: - windshift-data:/data volumes: windshift-data: ``` ### Before First Startup Generate an `SSO_SECRET` and create a `.env` file before running `docker compose up`. This secret secures both SSO state and session cookies. ```bash # Generate the secret openssl rand -hex 32 ``` Add it to a `.env` file alongside your other settings: ```bash DOMAIN=windshift.example.com PORT=8080 SSO_SECRET= POSTGRES_PASSWORD= # only needed for PostgreSQL LETSENCRYPT_EMAIL= # only needed for Traefik ``` ### With PostgreSQL To use PostgreSQL instead of SQLite, add a postgres service: ```yaml services: windshift: image: ghcr.io/windshiftapp/windshift:latest restart: unless-stopped ports: - "8080:8080" tmpfs: - /tmp:size=64M environment: - BASE_URL=https://windshift.example.com - SSO_SECRET=${SSO_SECRET} - POSTGRES_CONNECTION_STRING=postgres://windshift:${POSTGRES_PASSWORD}@postgres:5432/windshift?sslmode=disable - ATTACHMENT_PATH=/data/attachments volumes: - windshift-data:/data depends_on: postgres: condition: service_healthy postgres: image: postgres:17-alpine restart: unless-stopped environment: - POSTGRES_USER=windshift - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=windshift volumes: - postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U windshift"] interval: 5s timeout: 5s retries: 5 volumes: windshift-data: postgres-data: ``` ### With Traefik (HTTPS) Add Traefik for automatic HTTPS with Let's Encrypt: ```yaml services: windshift: image: ghcr.io/windshiftapp/windshift:latest restart: unless-stopped tmpfs: - /tmp:size=64M environment: - BASE_URL=https://${DOMAIN} - SSO_SECRET=${SSO_SECRET} - USE_PROXY=true - ALLOWED_HOSTS=${DOMAIN} - DB_PATH=/data/windshift.db - ATTACHMENT_PATH=/data/attachments volumes: - windshift-data:/data labels: - "traefik.enable=true" - "traefik.http.routers.windshift.rule=Host(`${DOMAIN}`)" - "traefik.http.routers.windshift.entrypoints=websecure" - "traefik.http.routers.windshift.tls.certresolver=letsencrypt" - "traefik.http.services.windshift.loadbalancer.server.port=8080" traefik: image: traefik:v3.4 restart: unless-stopped command: - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - letsencrypt-data:/letsencrypt volumes: windshift-data: letsencrypt-data: ``` When running behind a reverse proxy, always set: - `USE_PROXY=true` - Trusts `X-Forwarded-Proto` and `X-Forwarded-For` headers - `BASE_URL` - Public URL for generating links in emails, SSO redirects, and calendar feeds - `ALLOWED_HOSTS` - Restricts which hostnames Windshift will accept requests for ## Docker Image Details The official image uses a multi-stage build: 1. **Frontend build** - Node.js 25-alpine, runs `npm ci` and builds with Vite 2. **Backend build** - Go 1.26-alpine, compiles a static binary (`CGO_ENABLED=0`) 3. **Runtime** - Scratch image with CA certs and timezone data The final image runs as an unprivileged user (UID 65534) and exposes port 8080. ### SQLite with tmpfs When using SQLite in a scratch container, mount a tmpfs volume for the WAL files to avoid filesystem compatibility issues: ```yaml windshift: image: ghcr.io/windshiftapp/windshift:latest tmpfs: - /tmp:size=64M volumes: - windshift-data:/data ``` ## Optional Services Windshift supports additional companion services that run as separate containers: - **[LLM Inference](/docs/02-deployment/05-llm-service)** - Local AI model powering features like Plan My Day, Catch Me Up, and task decomposition (~2 GB RAM) - **[Logbook](/docs/02-deployment/06-logbook)** - Knowledge management and document ingestion service for team knowledge bases