Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.git
.venv
client/node_modules
client/dist
server/__pycache__
server/*.egg-info
server/.venv
server/.mypy_cache
server/.pytest_cache
server/.ruff_cache
*.pyc

# other dev folders
.scratch
.idea
.claude
.vscode
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Host ports exposed by the containers
SERVER_HOST_PORT=8880
CLIENT_HOST_PORT=5180

# Path to your copick config JSON on the host machine.
# This gets mounted read-only into the container at /data/copick_config.json.
COPICK_CONFIG_PATH=./copick_config.json

# Path to your copick data directory on the host machine.
# This gets mounted into the container at /data/copick_data.
# Make sure paths inside your copick config reference /data/copick_data.
COPICK_DATA_DIR=./data

# Base path prefix when running behind a reverse proxy (e.g., /viewer/copick-web).
# Leave empty for local development.
BASE_PATH=
75 changes: 75 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Build and Publish Docker Images

on:
push:
branches: [main]
workflow_dispatch:

env:
REGISTRY: ghcr.io

permissions:
contents: read
packages: write

jobs:
build-server:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3

- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/metadata-action@v5
id: meta
with:
images: ${{ env.REGISTRY }}/${{ github.repository }}-server
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=sha

- uses: docker/build-push-action@v6
with:
context: ./server
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

build-client:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3

- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/metadata-action@v5
id: meta
with:
images: ${{ env.REGISTRY }}/${{ github.repository }}-client
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=sha

- uses: docker/build-push-action@v6
with:
context: .
file: client/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
13 changes: 11 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,19 @@ pids
# JetBrains IDEs
.idea/

# VS Code
.vscode/
# VS Code (ignore all except shared config)
.vscode/*
!.vscode/tasks.json
!.vscode/extensions.json
*.code-workspace

# WIP Scratch folder
.scratch/

# Claude
.claude/
CLAUDE.md

# Vim
*.swp
*.swo
Expand Down
9 changes: 9 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"recommendations": [
"ms-vscode-remote.remote-containers",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-python.python",
"charliermarsh.ruff"
]
}
52 changes: 52 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Client: Lint",
"type": "shell",
"command": "podman compose -f compose-dev.yml exec client npm run lint",
"problemMatcher": "$eslint-stylish"
},
{
"label": "Client: Format",
"type": "shell",
"command": "podman compose -f compose-dev.yml exec client npm run format",
"problemMatcher": []
},
{
"label": "Server: Black",
"type": "shell",
"command": "podman compose -f compose-dev.yml exec server black src/",
"problemMatcher": []
},
{
"label": "Server: Ruff Check",
"type": "shell",
"command": "podman compose -f compose-dev.yml exec server ruff check src/",
"problemMatcher": {
"owner": "ruff",
"fileLocation": ["relative", "${workspaceFolder}/server"],
"pattern": {
"regexp": "^(.+):(\\d+):(\\d+): (\\w+) (.+)$",
"file": 1,
"line": 2,
"column": 3,
"code": 4,
"message": 5
}
}
},
{
"label": "Server: Ruff Fix",
"type": "shell",
"command": "podman compose -f compose-dev.yml exec server ruff check src/ --fix",
"problemMatcher": []
},
{
"label": "Lint All",
"dependsOn": ["Client: Lint", "Server: Black", "Server: Ruff Check"],
"dependsOrder": "parallel",
"problemMatcher": []
}
]
}
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,52 @@ Examples:

## Development

### Server Development
We offer dev containers for ease of use or manual dev setups.

### Docker/Podman Compose
Pre-requisites: Podman (recommended) or Docker installed with Compose extension. Check if installed with `docker-compose version` or `podman compose version`
- create .env file using .env.example as template.
- obtain or use a copick project. Modify config.json's `overlay_root` parameter to `local:/data/copick_data/`
```
# Example .env
# Host ports exposed by the containers
SERVER_HOST_PORT=8880
CLIENT_HOST_PORT=5180

# Please modify below with path to copick project, pointing to the copick config json.
# NOTE: In the config.json, please change "overlay_root": "local:/data/copick_data/"
# The config gets mounted read-only into the container at /data/copick_config.json.
COPICK_CONFIG_PATH=.scratch/NPC1_DEMO/configNPC1.json

# Path to your copick data directory on the host machine.
# This gets mounted into the container at /data/copick_data.
# Make sure paths inside your copick config reference /data/copick_data.
COPICK_DATA_DIR=.scratch/NPC1_DEMO/NPC1_DEMO/project
```

Starting the servers
```bash
# start frontend and backend
podman compose -f compose-dev.yml up
# when done developing, shutdown with ctrl+c or
podman compose -f compose-dev.yml down
```
Runs both server and client with hot reload, refreshing on code updates. Access on http://localhost:8880 or specified SERVER_HOST_PORT

Access backend API (FastAPI) with http://localhost:8880/docs

To build & check production images
```bash
# uses compose.yml
podman compose up --build -d
# when done developing, shutdown
podman compose down
```

### Manual
If you do not wish to run on Docker, these are the manual steps. Please see prerequisites section.

#### Server Development

```bash
cd server
Expand All @@ -78,7 +123,7 @@ export COPICK_CONFIG_PATH=/path/to/config.json
uvicorn copick_web.app.main:app --reload --port 8000
```

### Client Development
#### Client Development

```bash
cd client
Expand Down
5 changes: 5 additions & 0 deletions client/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.git
node_modules
dist
.eslintcache
coverage
28 changes: 28 additions & 0 deletions client/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
FROM node:22-slim AS build
# idetik requires newer npm version
RUN npm install -g npm@11
WORKDIR /app
COPY client/package*.json ./
# --ignore-scripts avoids ETXTBSY error with esbuild on Podman overlay fs
RUN npm ci --ignore-scripts && node node_modules/esbuild/install.js
COPY client/ .
# Local builds: read BASE_PATH from .env if present.
# CI builds (no .env): fall back to placeholder for runtime replacement.
COPY .env* .
RUN if grep -q '^BASE_PATH=' .env 2>/dev/null; then \
export $(grep '^BASE_PATH=' .env | head -1 | xargs); \
else \
export BASE_PATH="/__COPICK_BASE_PATH__"; \
fi && \
npm run build

FROM docker.io/library/nginx:alpine
# Run workers as root (safe in rootless Podman — maps to host user)
RUN sed -i 's/user nginx;/user root;/' /etc/nginx/nginx.conf
COPY --from=build /app/dist /usr/share/nginx/html
RUN chmod -R a+rX /usr/share/nginx/html
COPY client/nginx.conf /etc/nginx/conf.d/default.conf
# Entrypoint script replaces BASE_PATH placeholder at container startup
COPY client/docker-entrypoint.sh /docker-entrypoint.d/40-base-path.sh
RUN chmod +x /docker-entrypoint.d/40-base-path.sh
EXPOSE 80
10 changes: 10 additions & 0 deletions client/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM node:22-slim
# idetik requires newer npm version
RUN npm install -g npm@11

WORKDIR /app

COPY package*.json ./
RUN npm install

CMD ["sh", "-c", "npm install && npm run dev -- --host 0.0.0.0"]
9 changes: 9 additions & 0 deletions client/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh
set -e

BASE_PATH="${BASE_PATH:-}"

# Replace build-time placeholder with runtime BASE_PATH in all static assets.
# This is a no-op for local builds where the real BASE_PATH was baked in.
find /usr/share/nginx/html -type f \( -name '*.html' -o -name '*.js' -o -name '*.css' \) \
-exec sed -i "s|/__COPICK_BASE_PATH__|${BASE_PATH}|g" {} +
34 changes: 34 additions & 0 deletions client/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
server {
listen 80;

root /usr/share/nginx/html;
index index.html;

# API proxy to server container
location /api/ {
proxy_pass http://server:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# Zarr proxy to server container
location /zarr/ {
proxy_pass http://server:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# Health check passthrough
location /health {
proxy_pass http://server:8000;
}

# SPA fallback — serve index.html for client-side routing
location / {
try_files $uri $uri/ /index.html;
}
}
Loading
Loading