Skip to content

Commit 266ff4c

Browse files
committed
Add Dockerfile.render and make deployment idempotent
- Add docker/Dockerfile.render (copy of Dockerfile.fly) - Make deploy script idempotent (detect existing service and update) - Auto-detect current git branch for deployment - Update render.yaml to use Dockerfile.render - Script now updates existing service instead of creating duplicate
1 parent 96b95c8 commit 266ff4c

File tree

3 files changed

+216
-47
lines changed

3 files changed

+216
-47
lines changed

docker/Dockerfile.render

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
FROM python:3.12-slim
2+
3+
# Cache busting argument (set during build to force fresh layers)
4+
ARG CACHEBUST=4
5+
6+
# Use CACHEBUST to invalidate cache when needed (this layer changes when CACHEBUST changes)
7+
RUN echo "Cache bust: $CACHEBUST" > /tmp/cachebust
8+
9+
# Install system dependencies including nginx
10+
RUN apt-get update && apt-get install -y --no-install-recommends \
11+
git \
12+
build-essential \
13+
curl \
14+
cmake \
15+
nginx \
16+
apache2-utils \
17+
net-tools \
18+
netcat-openbsd \
19+
&& apt-get clean && rm -rf /var/lib/apt/lists/*
20+
21+
WORKDIR /opt/dagster/app
22+
23+
# Copy requirements and install Python dependencies
24+
COPY requirements.txt requirements-dashboard.txt ./
25+
RUN pip install --no-cache-dir -r requirements.txt && \
26+
pip install --no-cache-dir -r requirements-dashboard.txt
27+
28+
# Copy application code
29+
COPY anomstack ./anomstack
30+
COPY dashboard ./dashboard
31+
COPY metrics ./metrics
32+
33+
# Capture git commit hash for version info
34+
ARG ANOMSTACK_BUILD_HASH
35+
ENV ANOMSTACK_BUILD_HASH=${ANOMSTACK_BUILD_HASH}
36+
37+
# Copy configuration files
38+
COPY dagster_fly.yaml ./dagster_home/dagster.yaml
39+
COPY workspace.yaml ./dagster_home/workspace.yaml
40+
# Copy startup scripts (use CACHEBUST arg above to ensure fresh copy when needed)
41+
COPY scripts/deployment/start.sh /opt/dagster/start.sh
42+
COPY scripts/deployment/debug_grpc.sh /opt/dagster/debug_grpc.sh
43+
44+
# Verify files were copied and create any missing directories
45+
RUN ls -la ./dagster_home/ && \
46+
mkdir -p /opt/dagster/dagster_home && \
47+
cp -r ./dagster_home/* /opt/dagster/dagster_home/ && \
48+
ls -la /opt/dagster/dagster_home/ && \
49+
chmod +x /opt/dagster/start.sh && \
50+
chmod +x /opt/dagster/debug_grpc.sh
51+
52+
# Create necessary directories for SQLite storage and artifacts
53+
# Clear any existing Dagster storage to force fresh workspace loading
54+
RUN mkdir -p /data/models /data/dagster_storage /data/artifacts /tmp/dagster/compute_logs && \
55+
rm -rf /data/dagster_storage/* 2>/dev/null || true
56+
57+
# Environment setup
58+
ENV DAGSTER_HOME=/opt/dagster/dagster_home
59+
ENV PYTHONPATH=/opt/dagster/app
60+
ENV ANOMSTACK_DUCKDB_PATH=/data/anomstack.db
61+
ENV ANOMSTACK_MODEL_PATH=local:///data/models
62+
ENV ANOMSTACK_TABLE_KEY=metrics
63+
64+
# Copy example environment file
65+
COPY .example.env .env
66+
67+
# Health check script
68+
RUN echo '#!/bin/bash\n\
69+
if curl -f http://localhost:3000/server_info 2>/dev/null; then\n\
70+
echo "Dagster webserver healthy"\n\
71+
exit 0\n\
72+
else\n\
73+
echo "Dagster webserver not responding"\n\
74+
exit 1\n\
75+
fi' > /usr/local/bin/health-check.sh && chmod +x /usr/local/bin/health-check.sh
76+
77+
# Setup nginx
78+
COPY nginx.conf /etc/nginx/nginx.conf
79+
# Password file will be created at runtime from environment variables
80+
81+
# Create nginx directories
82+
RUN mkdir -p /var/log/nginx && \
83+
chown -R www-data:www-data /var/log/nginx && \
84+
ln -sf /dev/stdout /var/log/nginx/access.log && \
85+
ln -sf /dev/stderr /var/log/nginx/error.log
86+
87+
# Expose ports (nginx will handle routing)
88+
EXPOSE 80
89+
90+
# Health check
91+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
92+
CMD /usr/local/bin/health-check.sh
93+
94+
# Use simple startup script
95+
CMD ["/opt/dagster/start.sh"]

render.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ services:
66
branch: main
77
region: oregon # Choose: oregon, frankfurt, singapore, ohio, virginia
88
plan: starter # starter, standard, pro, pro_plus (upgrade as needed)
9-
dockerfilePath: ./docker/Dockerfile.fly
9+
dockerfilePath: ./docker/Dockerfile.render
1010
dockerContext: ./
1111
numInstances: 1
1212

scripts/deployment/deploy_render_api.sh

Lines changed: 120 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ REGION="oregon"
2323
PLAN="starter"
2424
DISK_SIZE_GB=10
2525
GITHUB_REPO="https://github.com/andrewm4894/anomstack"
26-
BRANCH="main"
26+
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
2727
CONFIRM=false
2828

2929
# Parse arguments
@@ -44,6 +44,7 @@ echo -e "${BLUE}╚════════════════════
4444
echo ""
4545
echo " Service Name: $SERVICE_NAME"
4646
echo " Profile: $PROFILE"
47+
echo " Branch: $BRANCH"
4748
echo " Region: $REGION"
4849
echo " Plan: $PLAN"
4950
echo " Disk Size: ${DISK_SIZE_GB}GB"
@@ -79,6 +80,21 @@ fi
7980

8081
echo -e "${GREEN}✅ Owner ID: $OWNER_ID${NC}"
8182

83+
# Check if service already exists
84+
echo -e "${BLUE}🔍 Checking if service '$SERVICE_NAME' already exists...${NC}"
85+
EXISTING_SERVICE=$(curl -s -H "Authorization: Bearer $RENDER_API_KEY" \
86+
"https://api.render.com/v1/services" | jq -r ".[] | select(.service.name == \"$SERVICE_NAME\") | .service.id")
87+
88+
if [ -n "$EXISTING_SERVICE" ]; then
89+
echo -e "${YELLOW}⚠️ Service '$SERVICE_NAME' already exists (ID: $EXISTING_SERVICE)${NC}"
90+
echo -e "${YELLOW}⚠️ Will update existing service instead of creating new one${NC}"
91+
SERVICE_ID="$EXISTING_SERVICE"
92+
UPDATE_MODE=true
93+
else
94+
echo -e "${GREEN}✅ Service does not exist, will create new service${NC}"
95+
UPDATE_MODE=false
96+
fi
97+
8298
# Load profile environment variables
8399
PROFILE_FILE="$PROJECT_ROOT/profiles/${PROFILE}.env"
84100
if [ ! -f "$PROFILE_FILE" ]; then
@@ -148,7 +164,7 @@ PAYLOAD=$(cat <<EOF
148164
"sizeGB": $DISK_SIZE_GB
149165
},
150166
"envSpecificDetails": {
151-
"dockerfilePath": "./docker/Dockerfile.fly",
167+
"dockerfilePath": "./docker/Dockerfile.render",
152168
"dockerContext": ".",
153169
"dockerCommand": ""
154170
},
@@ -174,52 +190,110 @@ else
174190
fi
175191

176192
echo ""
177-
echo -e "${BLUE}🚀 Creating service...${NC}"
178-
179-
# Create service via API
180-
RESPONSE=$(curl -s -w "\n%{http_code}" \
181-
-X POST "https://api.render.com/v1/services" \
182-
-H "Authorization: Bearer $RENDER_API_KEY" \
183-
-H "Content-Type: application/json" \
184-
-H "Accept: application/json" \
185-
-d "$PAYLOAD")
186-
187-
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
188-
BODY=$(echo "$RESPONSE" | sed '$d')
189-
190-
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
191-
echo -e "${GREEN}✅ Service created successfully!${NC}"
192-
echo ""
193-
194-
SERVICE_ID=$(echo "$BODY" | jq -r '.service.id // .id' 2>/dev/null)
195-
SERVICE_URL=$(echo "$BODY" | jq -r '.service.serviceDetails.url // .serviceDetails.url // empty' 2>/dev/null)
196-
197-
echo -e "${GREEN}Service Details:${NC}"
198-
echo " Service ID: $SERVICE_ID"
199-
[ -n "$SERVICE_URL" ] && echo " URL: https://$SERVICE_URL"
200-
echo ""
201193

202-
echo -e "${BLUE}📋 Next Steps:${NC}"
203-
echo " 1. View deployment: https://dashboard.render.com/web/$SERVICE_ID"
204-
echo " 2. Set ANOMSTACK_ADMIN_PASSWORD secret:"
205-
echo " - Go to service → Environment"
206-
echo " - Mark ANOMSTACK_ADMIN_PASSWORD as secret"
207-
echo " - Set your admin password"
208-
echo " 3. Wait for build to complete (~5-10 minutes)"
209-
echo " 4. Access dashboard: https://$SERVICE_URL"
210-
echo ""
211-
212-
echo -e "${BLUE}💡 View logs:${NC}"
213-
echo " export RENDER_SERVICE_ID=$SERVICE_ID"
214-
echo " make render-logs"
215-
echo ""
194+
if [ "$UPDATE_MODE" = true ]; then
195+
echo -e "${BLUE}🔄 Updating existing service...${NC}"
196+
197+
# Update service via API (PATCH)
198+
UPDATE_PAYLOAD=$(echo "$PAYLOAD" | jq 'del(.type, .ownerId, .name)')
199+
200+
RESPONSE=$(curl -s -w "\n%{http_code}" \
201+
-X PATCH "https://api.render.com/v1/services/$SERVICE_ID" \
202+
-H "Authorization: Bearer $RENDER_API_KEY" \
203+
-H "Content-Type: application/json" \
204+
-H "Accept: application/json" \
205+
-d "$UPDATE_PAYLOAD")
206+
207+
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
208+
BODY=$(echo "$RESPONSE" | sed '$d')
209+
210+
if [ "$HTTP_CODE" = "200" ]; then
211+
echo -e "${GREEN}✅ Service updated successfully!${NC}"
212+
echo ""
213+
214+
SERVICE_URL=$(echo "$BODY" | jq -r '.service.serviceDetails.url // .serviceDetails.url // empty' 2>/dev/null)
215+
216+
echo -e "${GREEN}Service Details:${NC}"
217+
echo " Service ID: $SERVICE_ID"
218+
[ -n "$SERVICE_URL" ] && echo " URL: https://$SERVICE_URL"
219+
echo ""
220+
221+
# Trigger new deploy after update
222+
echo -e "${BLUE}🚀 Triggering new deployment...${NC}"
223+
DEPLOY_RESPONSE=$(curl -s -X POST "https://api.render.com/v1/services/$SERVICE_ID/deploys" \
224+
-H "Authorization: Bearer $RENDER_API_KEY" \
225+
-H "Content-Type: application/json" \
226+
-d '{"clearCache": "do_not_clear"}')
227+
228+
DEPLOY_ID=$(echo "$DEPLOY_RESPONSE" | jq -r '.id')
229+
echo -e "${GREEN}✅ Deploy triggered (ID: $DEPLOY_ID)${NC}"
230+
else
231+
echo -e "${RED}❌ Failed to update service (HTTP $HTTP_CODE)${NC}"
232+
echo ""
233+
echo "Response:"
234+
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
235+
echo ""
236+
exit 1
237+
fi
216238
else
217-
echo -e "${RED}❌ Failed to create service (HTTP $HTTP_CODE)${NC}"
218-
echo ""
219-
echo "Response:"
220-
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
221-
echo ""
222-
exit 1
239+
echo -e "${BLUE}🚀 Creating new service...${NC}"
240+
241+
# Create service via API
242+
RESPONSE=$(curl -s -w "\n%{http_code}" \
243+
-X POST "https://api.render.com/v1/services" \
244+
-H "Authorization: Bearer $RENDER_API_KEY" \
245+
-H "Content-Type: application/json" \
246+
-H "Accept: application/json" \
247+
-d "$PAYLOAD")
248+
249+
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
250+
BODY=$(echo "$RESPONSE" | sed '$d')
251+
252+
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
253+
echo -e "${GREEN}✅ Service created successfully!${NC}"
254+
echo ""
255+
256+
SERVICE_ID=$(echo "$BODY" | jq -r '.service.id // .id' 2>/dev/null)
257+
SERVICE_URL=$(echo "$BODY" | jq -r '.service.serviceDetails.url // .serviceDetails.url // empty' 2>/dev/null)
258+
259+
echo -e "${GREEN}Service Details:${NC}"
260+
echo " Service ID: $SERVICE_ID"
261+
[ -n "$SERVICE_URL" ] && echo " URL: https://$SERVICE_URL"
262+
echo ""
263+
264+
# Trigger initial deploy
265+
echo -e "${BLUE}🚀 Triggering initial deployment...${NC}"
266+
DEPLOY_RESPONSE=$(curl -s -X POST "https://api.render.com/v1/services/$SERVICE_ID/deploys" \
267+
-H "Authorization: Bearer $RENDER_API_KEY" \
268+
-H "Content-Type: application/json" \
269+
-d '{"clearCache": "do_not_clear"}')
270+
271+
DEPLOY_ID=$(echo "$DEPLOY_RESPONSE" | jq -r '.id')
272+
echo -e "${GREEN}✅ Deploy triggered (ID: $DEPLOY_ID)${NC}"
273+
else
274+
echo -e "${RED}❌ Failed to create service (HTTP $HTTP_CODE)${NC}"
275+
echo ""
276+
echo "Response:"
277+
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
278+
echo ""
279+
exit 1
280+
fi
223281
fi
224282

283+
echo ""
284+
echo -e "${BLUE}📋 Next Steps:${NC}"
285+
echo " 1. View deployment: https://dashboard.render.com/web/$SERVICE_ID"
286+
echo " 2. Set ANOMSTACK_ADMIN_PASSWORD secret (if not already set):"
287+
echo " - Go to service → Environment"
288+
echo " - Mark ANOMSTACK_ADMIN_PASSWORD as secret"
289+
echo " - Set your admin password"
290+
echo " 3. Wait for build to complete (~5-10 minutes)"
291+
echo " 4. Access dashboard: https://anomstack-demo.onrender.com"
292+
echo ""
293+
294+
echo -e "${BLUE}💡 View logs:${NC}"
295+
echo " export RENDER_SERVICE_ID=$SERVICE_ID"
296+
echo " make render-logs"
297+
echo ""
298+
225299
echo -e "${GREEN}✅ Deployment initiated successfully!${NC}"

0 commit comments

Comments
 (0)