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
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:latestNote: 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:
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.
# Generate the secret
openssl rand -hex 32Add it to a .env file alongside your other settings:
DOMAIN=windshift.example.com
PORT=8080
SSO_SECRET=<your-generated-secret>
POSTGRES_PASSWORD= # only needed for PostgreSQL
LETSENCRYPT_EMAIL= # only needed for TraefikWith PostgreSQL
To use PostgreSQL instead of SQLite, add a postgres service:
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:
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- TrustsX-Forwarded-ProtoandX-Forwarded-ForheadersBASE_URL- Public URL for generating links in emails, SSO redirects, and calendar feedsALLOWED_HOSTS- Restricts which hostnames Windshift will accept requests for
Docker Image Details
The official image uses a multi-stage build:
- Frontend build - Node.js 25-alpine, runs
npm ciand builds with Vite - Backend build - Go 1.26-alpine, compiles a static binary (
CGO_ENABLED=0) - 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:
windshift:
image: ghcr.io/windshiftapp/windshift:latest
tmpfs:
- /tmp:size=64M
volumes:
- windshift-data:/dataOptional Services
Windshift supports additional companion services that run as separate containers:
- LLM Inference - Local AI model powering features like Plan My Day, Catch Me Up, and task decomposition (~2 GB RAM)
- Logbook - Knowledge management and document ingestion service for team knowledge bases