Summary
Add first-class Docker support to JavaScriptSolidServer, enabling users to deploy with a single docker run command. This is a P0 priority item as Docker is the expected deployment method for modern self-hosted software.
Difficulty: 30/100
Estimated Effort: 2-3 days
Dependencies: None
Current State
- ❌ No
Dockerfile
- ❌ No
docker-compose.yml
- ❌ No
.dockerignore
- ❌ No container registry images
- ❌ No health check endpoint
- ❌ No Docker documentation
The only Docker reference is for running CTH tests (external container).
Proposed Implementation
1. Dockerfile (Multi-Stage Build)
# ============================================
# Stage 1: Builder
# ============================================
FROM node:22-alpine AS builder
WORKDIR /app
# Install dependencies first (cache optimization)
COPY package*.json ./
RUN npm ci
# Copy source
COPY . .
# ============================================
# Stage 2: Production
# ============================================
FROM node:22-alpine AS production
# Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init
# Create non-root user
RUN addgroup -g 1001 -S jss && \
adduser -S -u 1001 -G jss jss
WORKDIR /app
# Copy only production dependencies and source
COPY --from=builder --chown=jss:jss /app/node_modules ./node_modules
COPY --from=builder --chown=jss:jss /app/package.json ./
COPY --from=builder --chown=jss:jss /app/bin ./bin
COPY --from=builder --chown=jss:jss /app/src ./src
# Create data directory
RUN mkdir -p /data && chown jss:jss /data
# Switch to non-root user
USER jss
# Environment defaults
ENV NODE_ENV=production
ENV JSS_PORT=3000
ENV JSS_HOST=0.0.0.0
ENV JSS_ROOT=/data
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/.well-known/solid || exit 1
# Use dumb-init as PID 1
ENTRYPOINT ["dumb-init", "--"]
# Start server
CMD ["node", "bin/jss.js", "start"]
Key Dockerfile Features
| Feature |
Benefit |
| Multi-stage build |
~70% smaller image (node_modules dev deps excluded) |
| Alpine base |
Minimal attack surface, ~5x smaller than Debian |
| Non-root user |
Security best practice, prevents privilege escalation |
| dumb-init |
Proper signal handling, zombie process reaping |
| Layer caching |
package.json first = faster rebuilds |
| Health check |
Container orchestration support |
2. Docker Compose Examples
Basic (docker-compose.yml)
services:
jss:
image: ghcr.io/javascriptsolidserver/jss:latest
ports:
- "3000:3000"
volumes:
- jss-data:/data
environment:
- JSS_MULTIUSER=true
- JSS_IDP=true
restart: unless-stopped
volumes:
jss-data:
Production with SSL (docker-compose.prod.yml)
services:
jss:
image: ghcr.io/javascriptsolidserver/jss:latest
ports:
- "443:3000"
volumes:
- ./data:/data
- ./ssl:/ssl:ro
environment:
- JSS_PORT=3000
- JSS_SSL_KEY=/ssl/key.pem
- JSS_SSL_CERT=/ssl/cert.pem
- JSS_MULTIUSER=true
- JSS_IDP=true
- JSS_CONNEG=true
- JSS_NOTIFICATIONS=true
- JSS_DEFAULT_QUOTA=100MB
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/.well-known/solid"]
interval: 30s
timeout: 10s
retries: 3
Full Stack with Reverse Proxy (docker-compose.full.yml)
services:
jss:
image: ghcr.io/javascriptsolidserver/jss:latest
volumes:
- jss-data:/data
environment:
- JSS_MULTIUSER=true
- JSS_IDP=true
- JSS_CONNEG=true
- JSS_NOTIFICATIONS=true
- JSS_ACTIVITYPUB=true
- JSS_NOSTR=true
restart: unless-stopped
networks:
- internal
caddy:
image: caddy:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
depends_on:
- jss
restart: unless-stopped
networks:
- internal
volumes:
jss-data:
caddy-data:
caddy-config:
networks:
internal:
Single-User Personal Server
services:
jss:
image: ghcr.io/javascriptsolidserver/jss:latest
ports:
- "3000:3000"
volumes:
- ./data:/data
environment:
- JSS_SINGLE_USER=true
- JSS_SINGLE_USER_NAME=me
- JSS_IDP=true
- JSS_CONNEG=true
- JSS_MASHLIB=true
restart: unless-stopped
3. .dockerignore
# Dependencies
node_modules
# Git
.git
.gitignore
# IDE
.vscode
.idea
*.swp
*.swo
# Testing
test
tests
*.test.js
coverage
.nyc_output
# Documentation
*.md
!README.md
docs
# CI/CD
.github
.gitlab-ci.yml
.travis.yml
# Development
.env
.env.*
docker-compose*.yml
Dockerfile*
# Data (should be mounted as volume)
data
*.db
# Misc
.DS_Store
Thumbs.db
*.log
4. GitHub Actions Workflow
.github/workflows/docker.yml
name: Docker Build & Push
on:
push:
branches: [main, gh-pages]
tags: ['v*']
pull_request:
branches: [main, gh-pages]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix=
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/gh-pages' }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
- name: Generate SBOM
if: github.event_name != 'pull_request'
uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
Workflow Features
| Feature |
Benefit |
| Multi-platform |
linux/amd64 + linux/arm64 (Raspberry Pi, M1/M2 Macs) |
| Semantic versioning |
Tags like v1.0.0 create 1.0.0, 1.0, 1 tags |
| Cache |
GitHub Actions cache for faster builds |
| SBOM |
Software Bill of Materials for security scanning |
| PR builds |
Build but don't push on PRs (test Dockerfile) |
5. Health Check Endpoint
Add /.well-known/solid or /health endpoint:
// src/server.js - add health check route
fastify.get('/health', async (request, reply) => {
return {
status: 'healthy',
version: process.env.npm_package_version || 'unknown',
uptime: process.uptime(),
timestamp: new Date().toISOString()
};
});
// Or use existing /.well-known/solid which returns server metadata
6. Documentation Updates
README.md Docker Section
## Docker
### Quick Start
```bash
docker run -d \
--name jss \
-p 3000:3000 \
-v jss-data:/data \
ghcr.io/javascriptsolidserver/jss:latest
With Configuration
docker run -d \
--name jss \
-p 3000:3000 \
-v ./data:/data \
-e JSS_MULTIUSER=true \
-e JSS_IDP=true \
-e JSS_CONNEG=true \
ghcr.io/javascriptsolidserver/jss:latest
Docker Compose
curl -O https://raw.githubusercontent.com/JavaScriptSolidServer/JavaScriptSolidServer/main/docker-compose.yml
docker compose up -d
Available Tags
| Tag |
Description |
latest |
Latest stable release |
x.y.z |
Specific version |
x.y |
Latest patch of minor version |
x |
Latest minor of major version |
sha-abc123 |
Specific commit |
Environment Variables
All JSS_* environment variables are supported. See Configuration for full list.
---
## Industry Comparison
| Software | Docker Support |
|----------|----------------|
| **Community Solid Server** | ✅ Official image `solidproject/community-server` |
| **Nextcloud** | ✅ Official image, multiple variants (fpm, apache) |
| **Mastodon** | ✅ Official image, comprehensive compose files |
| **Gitea/Forgejo** | ✅ Official images, rootless variants |
| **Vaultwarden** | ✅ Official image `vaultwarden/server` |
| **PocketBase** | ✅ Official image, single binary friendly |
| **Matrix Synapse** | ✅ Official image `matrixdotorg/synapse` |
| **JSS (current)** | ❌ No Docker support |
All major self-hosted software provides official Docker images. This is table stakes.
---
## Image Size Estimates
| Stage | Estimated Size |
|-------|----------------|
| Base `node:22` | ~1.1 GB |
| Base `node:22-alpine` | ~180 MB |
| With dependencies | ~250 MB |
| Multi-stage final | ~80-100 MB |
Comparison:
- Community Solid Server: ~300 MB
- PocketBase: ~50 MB (Go binary)
- Nextcloud: ~800 MB (PHP + Apache)
---
## Implementation Checklist
- [ ] Create `Dockerfile` with multi-stage build
- [ ] Create `.dockerignore`
- [ ] Create `docker-compose.yml` (basic)
- [ ] Create `docker-compose.prod.yml` (with SSL)
- [ ] Create `docker-compose.full.yml` (with Caddy)
- [ ] Add health check endpoint (`/health`)
- [ ] Create GitHub Actions workflow
- [ ] Test on `linux/amd64`
- [ ] Test on `linux/arm64`
- [ ] Update README with Docker section
- [ ] Add example Caddyfile
- [ ] First release to ghcr.io
---
## Difficulty Breakdown
| Component | Difficulty |
|-----------|------------|
| Dockerfile | 20/100 |
| .dockerignore | 5/100 |
| docker-compose files | 15/100 |
| GitHub Actions workflow | 25/100 |
| Health endpoint | 10/100 |
| Documentation | 10/100 |
| Multi-arch testing | 20/100 |
| **Total** | 30/100 |
---
## Security Considerations
1. **Non-root user** - Container runs as UID 1001, not root
2. **Read-only root filesystem** - Can add `--read-only` flag
3. **No shell** - Consider `FROM scratch` or distroless for minimal attack surface
4. **SBOM generation** - Track dependencies for vulnerability scanning
5. **Signed images** - Consider sigstore/cosign for image signing
6. **Secrets handling** - Document proper secrets management (Docker secrets, env files)
---
## Future Enhancements
1. **Rootless variant** - For environments requiring rootless containers
2. **Distroless variant** - Minimal image without shell
3. **Kubernetes Helm chart** - For K8s deployments
4. **Docker Hub mirror** - Alternative registry
5. **Watchtower compatibility** - Auto-update support
---
## Related Issues
- #97 - Presets (Docker compose can use preset env var)
- #98 - CLI wizard (`jss generate docker` command)
- #95 - Web onboarding (works same in Docker)
- #96 - Admin panel (accessible via Docker)
---
## References
- [Node.js Docker Best Practices](https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md)
- [Docker Multi-Stage Builds](https://docs.docker.com/build/building/multi-stage/)
- [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry)
- [Docker Build Push Action](https://github.com/docker/build-push-action)
- [Community Solid Server Docker](https://hub.docker.com/r/solidproject/community-server)
Summary
Add first-class Docker support to JavaScriptSolidServer, enabling users to deploy with a single
docker runcommand. This is a P0 priority item as Docker is the expected deployment method for modern self-hosted software.Difficulty: 30/100
Estimated Effort: 2-3 days
Dependencies: None
Current State
Dockerfiledocker-compose.yml.dockerignoreThe only Docker reference is for running CTH tests (external container).
Proposed Implementation
1. Dockerfile (Multi-Stage Build)
Key Dockerfile Features
2. Docker Compose Examples
Basic (
docker-compose.yml)Production with SSL (
docker-compose.prod.yml)Full Stack with Reverse Proxy (
docker-compose.full.yml)Single-User Personal Server
3.
.dockerignore4. GitHub Actions Workflow
.github/workflows/docker.ymlWorkflow Features
linux/amd64+linux/arm64(Raspberry Pi, M1/M2 Macs)v1.0.0create1.0.0,1.0,1tags5. Health Check Endpoint
Add
/.well-known/solidor/healthendpoint:6. Documentation Updates
README.md Docker Section
With Configuration
Docker Compose
Available Tags
latestx.y.zx.yxsha-abc123Environment Variables
All
JSS_*environment variables are supported. See Configuration for full list.