Skip to content

Commit d64701f

Browse files
cfsmp3claude
andauthored
docs: add HTTPS production deployment guide with nginx reverse proxy (#402)
Add configuration examples and documentation for deploying CCSync with HTTPS using nginx as a reverse proxy with Let's Encrypt SSL certificates. New files: - production/example.frontend.env: Example frontend environment variables - production/example.nginx.conf: nginx reverse proxy configuration with SSL termination for the main site (port 443) and sync server (port 8080) Updated: - production/README.md: Reorganized into three deployment options: 1. Docker (HTTP only, for local/dev) 2. Docker with nginx reverse proxy (HTTPS, recommended for production) 3. Kubernetes The nginx config includes: - HTTP to HTTPS redirect - SSL configuration with modern cipher suites - Proxy rules for all backend API routes - WebSocket support for /ws endpoint - SSL termination for taskchampion sync server on port 8080 Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2b3d043 commit d64701f

3 files changed

Lines changed: 317 additions & 11 deletions

File tree

production/README.md

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,136 @@
1-
# To run using Docker:
1+
# Production Deployment
22

3-
1. Create a file named .backend.env with the following attributes:
3+
## Option 1: Docker (HTTP only, for local/development)
44

5-
```
5+
1. Create a file named `.backend.env` with the following attributes:
6+
7+
```bash
68
CLIENT_ID="client_ID" # Google Auth Secret from Prerequisites
79
CLIENT_SEC="client_SECRET" # Google Auth Secret from Prerequisites
810
REDIRECT_URL_DEV="http://localhost:8000/auth/callback"
911
SESSION_KEY="generate a secret key using 'openssl rand -hex 32'"
1012
FRONTEND_ORIGIN_DEV="http://localhost" # URL of the web frontend to avoid CORS errors
11-
CONTAINER_ORIGIN="http://YOUR_CONTAINER_NAME:8080/" # Deployed taskchampion-sync-server container, default is production-syncserver-1
13+
CONTAINER_ORIGIN="http://production-syncserver-1:8080/" # Deployed taskchampion-sync-server container
1214
```
1315

14-
2. Run docker-compose pull to pull the CCSync images.
15-
3. Run docker-compose up to start the project.
16+
2. Run `docker-compose pull` to pull the CCSync images.
17+
3. Run `docker-compose up` to start the project.
1618
4. The frontend should now be available at localhost:80, the backend at localhost:8000, and the sync server at localhost:8080
1719

18-
# To run the project using Kubernetes:
20+
## Option 2: Docker with nginx Reverse Proxy (HTTPS, recommended for production)
21+
22+
For production deployments with HTTPS, use nginx as a reverse proxy with Let's Encrypt SSL certificates.
23+
24+
### Prerequisites
25+
26+
- A domain name pointing to your server
27+
- Ubuntu/Debian server with root access
28+
29+
### Step 1: Install nginx and certbot
30+
31+
```bash
32+
sudo apt update
33+
sudo apt install -y nginx certbot python3-certbot-nginx
34+
```
35+
36+
### Step 2: Obtain SSL certificate
37+
38+
```bash
39+
sudo systemctl stop nginx
40+
sudo certbot certonly --standalone -d your-domain.com
41+
sudo systemctl start nginx
42+
```
43+
44+
### Step 3: Configure nginx
45+
46+
1. Copy `example.nginx.conf` to nginx sites:
47+
```bash
48+
sudo cp example.nginx.conf /etc/nginx/sites-available/ccsync
49+
```
50+
51+
2. Edit the file and replace `your-domain.com` with your actual domain:
52+
```bash
53+
sudo sed -i 's/your-domain.com/your-actual-domain.com/g' /etc/nginx/sites-available/ccsync
54+
```
55+
56+
3. Enable the site:
57+
```bash
58+
sudo ln -s /etc/nginx/sites-available/ccsync /etc/nginx/sites-enabled/
59+
sudo rm -f /etc/nginx/sites-enabled/default
60+
sudo nginx -t
61+
sudo systemctl reload nginx
62+
```
63+
64+
### Step 4: Update docker-compose.yml
65+
66+
Modify the port bindings to listen only on localhost (nginx will handle external traffic):
1967

20-
- From WSL / Linux / Git Bash (Please run as root in order to access frontend on port 80):
68+
```yaml
69+
services:
70+
frontend:
71+
ports:
72+
- "127.0.0.1:3000:80" # Changed from "80:80"
2173

74+
backend:
75+
ports:
76+
- "127.0.0.1:8000:8000" # Bind to localhost only
77+
78+
syncserver:
79+
ports:
80+
- "127.0.0.1:8081:8080" # Changed from "8080:8080"
2281
```
82+
83+
### Step 5: Create environment files
84+
85+
Create `.backend.env`:
86+
```bash
87+
CLIENT_ID="your-google-client-id"
88+
CLIENT_SEC="your-google-client-secret"
89+
REDIRECT_URL_DEV="https://your-domain.com/auth/callback"
90+
SESSION_KEY="$(openssl rand -hex 32)"
91+
FRONTEND_ORIGIN_DEV="https://your-domain.com"
92+
CONTAINER_ORIGIN="http://syncserver:8080/"
93+
```
94+
95+
Create `.frontend.env` (see `example.frontend.env`):
96+
```bash
97+
VITE_BACKEND_URL="https://your-domain.com/"
98+
VITE_FRONTEND_URL="https://your-domain.com"
99+
VITE_CONTAINER_ORIGIN="https://your-domain.com:8080/"
100+
```
101+
102+
### Step 6: Configure Google OAuth
103+
104+
In Google Cloud Console, add the redirect URI:
105+
- `https://your-domain.com/auth/callback`
106+
107+
### Step 7: Deploy
108+
109+
```bash
110+
docker-compose pull
111+
docker-compose up -d
112+
```
113+
114+
Your CCSync instance should now be available at `https://your-domain.com`
115+
116+
---
117+
118+
## Option 3: Kubernetes
119+
120+
From WSL / Linux / Git Bash (run as root to access frontend on port 80):
121+
122+
```bash
23123
chmod +x ./run-ports.sh
24124
./run-ports.sh
25125
```
26126

27-
- From PowerShell (Windows):
127+
From PowerShell (Windows):
28128

29-
```
129+
```bash
30130
bash .\run-ports.sh
31131
```
32132

33133
Notes:
34134

35135
- Ensure kubectl, tmux (for managing individual pods better) and access to your cluster are configured before running the script.
36-
- Edit production/backend-env-configmap.yaml and create secrets as needed before running the script.
136+
- Edit `backend-env-configmap.yaml` and create secrets as needed before running the script.

production/example.frontend.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
VITE_BACKEND_URL="https://your-domain.com/"
2+
VITE_FRONTEND_URL="https://your-domain.com"
3+
VITE_CONTAINER_ORIGIN="https://your-domain.com:8080/"

production/example.nginx.conf

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# CCSync nginx reverse proxy configuration
2+
# For production deployment with SSL (Let's Encrypt)
3+
#
4+
# Prerequisites:
5+
# 1. Install nginx: apt install nginx
6+
# 2. Install certbot: apt install certbot python3-certbot-nginx
7+
# 3. Obtain certificate: certbot certonly --standalone -d your-domain.com
8+
# 4. Copy this file to /etc/nginx/sites-available/ccsync
9+
# 5. Create symlink: ln -s /etc/nginx/sites-available/ccsync /etc/nginx/sites-enabled/
10+
# 6. Remove default: rm /etc/nginx/sites-enabled/default
11+
# 7. Test config: nginx -t
12+
# 8. Reload nginx: systemctl reload nginx
13+
#
14+
# Note: Update docker-compose.yml to bind ports to localhost only:
15+
# frontend: "127.0.0.1:3000:80"
16+
# backend: "127.0.0.1:8000:8000"
17+
# syncserver: "127.0.0.1:8081:8080"
18+
19+
# Redirect HTTP to HTTPS
20+
server {
21+
listen 80;
22+
listen [::]:80;
23+
server_name your-domain.com;
24+
25+
location / {
26+
return 301 https://$server_name$request_uri;
27+
}
28+
}
29+
30+
# Main HTTPS server
31+
server {
32+
listen 443 ssl http2;
33+
listen [::]:443 ssl http2;
34+
server_name your-domain.com;
35+
36+
# SSL certificates (update paths for your domain)
37+
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
38+
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
39+
40+
# SSL configuration
41+
ssl_protocols TLSv1.2 TLSv1.3;
42+
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
43+
ssl_prefer_server_ciphers off;
44+
ssl_session_cache shared:SSL:10m;
45+
ssl_session_timeout 1d;
46+
47+
# Backend API routes
48+
location /auth/ {
49+
proxy_pass http://127.0.0.1:8000;
50+
proxy_http_version 1.1;
51+
proxy_set_header Host $host;
52+
proxy_set_header X-Real-IP $remote_addr;
53+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
54+
proxy_set_header X-Forwarded-Proto $scheme;
55+
}
56+
57+
location /api/ {
58+
proxy_pass http://127.0.0.1:8000;
59+
proxy_http_version 1.1;
60+
proxy_set_header Host $host;
61+
proxy_set_header X-Real-IP $remote_addr;
62+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
63+
proxy_set_header X-Forwarded-Proto $scheme;
64+
}
65+
66+
location /tasks {
67+
proxy_pass http://127.0.0.1:8000;
68+
proxy_http_version 1.1;
69+
proxy_set_header Host $host;
70+
proxy_set_header X-Real-IP $remote_addr;
71+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
72+
proxy_set_header X-Forwarded-Proto $scheme;
73+
}
74+
75+
location /add-task {
76+
proxy_pass http://127.0.0.1:8000;
77+
proxy_http_version 1.1;
78+
proxy_set_header Host $host;
79+
proxy_set_header X-Real-IP $remote_addr;
80+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
81+
proxy_set_header X-Forwarded-Proto $scheme;
82+
}
83+
84+
location /edit-task {
85+
proxy_pass http://127.0.0.1:8000;
86+
proxy_http_version 1.1;
87+
proxy_set_header Host $host;
88+
proxy_set_header X-Real-IP $remote_addr;
89+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
90+
proxy_set_header X-Forwarded-Proto $scheme;
91+
}
92+
93+
location /modify-task {
94+
proxy_pass http://127.0.0.1:8000;
95+
proxy_http_version 1.1;
96+
proxy_set_header Host $host;
97+
proxy_set_header X-Real-IP $remote_addr;
98+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
99+
proxy_set_header X-Forwarded-Proto $scheme;
100+
}
101+
102+
location /complete-task {
103+
proxy_pass http://127.0.0.1:8000;
104+
proxy_http_version 1.1;
105+
proxy_set_header Host $host;
106+
proxy_set_header X-Real-IP $remote_addr;
107+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
108+
proxy_set_header X-Forwarded-Proto $scheme;
109+
}
110+
111+
location /complete-tasks {
112+
proxy_pass http://127.0.0.1:8000;
113+
proxy_http_version 1.1;
114+
proxy_set_header Host $host;
115+
proxy_set_header X-Real-IP $remote_addr;
116+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
117+
proxy_set_header X-Forwarded-Proto $scheme;
118+
}
119+
120+
location /delete-task {
121+
proxy_pass http://127.0.0.1:8000;
122+
proxy_http_version 1.1;
123+
proxy_set_header Host $host;
124+
proxy_set_header X-Real-IP $remote_addr;
125+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
126+
proxy_set_header X-Forwarded-Proto $scheme;
127+
}
128+
129+
location /delete-tasks {
130+
proxy_pass http://127.0.0.1:8000;
131+
proxy_http_version 1.1;
132+
proxy_set_header Host $host;
133+
proxy_set_header X-Real-IP $remote_addr;
134+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
135+
proxy_set_header X-Forwarded-Proto $scheme;
136+
}
137+
138+
location /sync/ {
139+
proxy_pass http://127.0.0.1:8000;
140+
proxy_http_version 1.1;
141+
proxy_set_header Host $host;
142+
proxy_set_header X-Real-IP $remote_addr;
143+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
144+
proxy_set_header X-Forwarded-Proto $scheme;
145+
}
146+
147+
location /health {
148+
proxy_pass http://127.0.0.1:8000;
149+
proxy_http_version 1.1;
150+
proxy_set_header Host $host;
151+
proxy_set_header X-Real-IP $remote_addr;
152+
}
153+
154+
location /docs {
155+
proxy_pass http://127.0.0.1:8000;
156+
proxy_http_version 1.1;
157+
proxy_set_header Host $host;
158+
proxy_set_header X-Real-IP $remote_addr;
159+
}
160+
161+
# WebSocket support
162+
location /ws {
163+
proxy_pass http://127.0.0.1:8000;
164+
proxy_http_version 1.1;
165+
proxy_set_header Upgrade $http_upgrade;
166+
proxy_set_header Connection "upgrade";
167+
proxy_set_header Host $host;
168+
proxy_set_header X-Real-IP $remote_addr;
169+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
170+
proxy_set_header X-Forwarded-Proto $scheme;
171+
proxy_read_timeout 86400;
172+
}
173+
174+
# Frontend (default)
175+
location / {
176+
proxy_pass http://127.0.0.1:3000;
177+
proxy_http_version 1.1;
178+
proxy_set_header Host $host;
179+
proxy_set_header X-Real-IP $remote_addr;
180+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
181+
proxy_set_header X-Forwarded-Proto $scheme;
182+
}
183+
}
184+
185+
# Taskchampion sync server (port 8080 with SSL)
186+
server {
187+
listen 8080 ssl http2;
188+
listen [::]:8080 ssl http2;
189+
server_name your-domain.com;
190+
191+
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
192+
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
193+
ssl_protocols TLSv1.2 TLSv1.3;
194+
195+
location / {
196+
proxy_pass http://127.0.0.1:8081;
197+
proxy_http_version 1.1;
198+
proxy_set_header Host $host;
199+
proxy_set_header X-Real-IP $remote_addr;
200+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
201+
proxy_set_header X-Forwarded-Proto $scheme;
202+
}
203+
}

0 commit comments

Comments
 (0)