Implement complete Docker Compose CI/CD setup with optimized builds
- Add multi-stage Dockerfile with dependency caching for both frontend and backend - Implement Docker Compose configuration with separate frontend/backend services - Configure Caddy as reverse proxy with proper WASM and static file serving - Add volume mounting for frontend assets shared between containers - Optimize build process with staged compilation and workspace handling - Add debug logging and WASM initialization tracking for production deployment - Update README with project motivation and "vibe coded" disclaimer 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
# Build artifacts
|
# Build artifacts
|
||||||
target/
|
target/
|
||||||
dist/
|
frontend/dist/
|
||||||
|
backend/target/
|
||||||
|
# Allow backend binary for multi-stage builds
|
||||||
|
!backend/target/release/backend
|
||||||
|
|
||||||
# Git
|
# Git
|
||||||
.git/
|
.git/
|
||||||
@@ -21,8 +24,18 @@ Thumbs.db
|
|||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
README.md
|
README.md
|
||||||
*.md
|
|
||||||
|
|
||||||
# Docker
|
# Development files
|
||||||
Dockerfile
|
CLAUDE.md
|
||||||
.dockerignore
|
*.txt
|
||||||
|
test_*.js
|
||||||
|
|
||||||
|
# Database files
|
||||||
|
*.db
|
||||||
|
calendar.db
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
**/tests/
|
||||||
|
|
||||||
|
# Migrations (not needed for builds)
|
||||||
|
migrations/
|
||||||
10
Caddyfile
Normal file
10
Caddyfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
default_sni rcjohnstone.com
|
||||||
|
key_type rsa4096
|
||||||
|
email c@rcjohnstone.com
|
||||||
|
}
|
||||||
|
|
||||||
|
:80, :443 {
|
||||||
|
root * /srv/www
|
||||||
|
file_server
|
||||||
|
}
|
||||||
125
Dockerfile
125
Dockerfile
@@ -1,67 +1,96 @@
|
|||||||
# Build stage
|
# Build stage
|
||||||
# ---------------------------------------
|
# -----------------------------------------------------------
|
||||||
FROM rust:alpine AS builder
|
FROM rust:alpine AS builder
|
||||||
|
|
||||||
# Install build dependencies
|
RUN apk add --no-cache musl-dev pkgconfig openssl-dev openssl-libs-static nodejs npm
|
||||||
RUN apk add --no-cache \
|
|
||||||
musl-dev \
|
|
||||||
pkgconfig \
|
|
||||||
openssl-dev \
|
|
||||||
nodejs \
|
|
||||||
npm
|
|
||||||
|
|
||||||
# Install trunk for building Yew apps
|
# Install trunk ahead of the compilation. This may break and then you'll have to update the version.
|
||||||
RUN cargo install trunk wasm-pack
|
RUN cargo install trunk@0.21.14 wasm-pack@0.13.1 wasm-bindgen-cli@0.2.100
|
||||||
|
|
||||||
# Add wasm32 target
|
|
||||||
RUN rustup target add wasm32-unknown-unknown
|
RUN rustup target add wasm32-unknown-unknown
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy dependency files
|
# Copy workspace files to maintain workspace structure
|
||||||
COPY Cargo.toml ./
|
COPY Cargo.toml Cargo.lock ./
|
||||||
COPY src ./src
|
COPY calendar-models ./calendar-models
|
||||||
|
COPY frontend/Cargo.toml ./frontend/
|
||||||
|
COPY frontend/Trunk.toml ./frontend/
|
||||||
|
COPY frontend/index.html ./frontend/
|
||||||
|
COPY frontend/styles.css ./frontend/
|
||||||
|
|
||||||
# Copy web assets
|
# Create empty backend directory to satisfy workspace
|
||||||
COPY index.html ./
|
RUN mkdir -p backend/src && \
|
||||||
COPY Trunk.toml ./
|
printf '[package]\nname = "calendar-backend"\nversion = "0.1.0"\nedition = "2021"\n\n[dependencies]\n' > backend/Cargo.toml && \
|
||||||
|
echo 'fn main() {}' > backend/src/main.rs
|
||||||
|
|
||||||
|
# Create dummy source files to build dependencies first
|
||||||
|
RUN mkdir -p frontend/src && \
|
||||||
|
echo "use web_sys::*; fn main() {}" > frontend/src/main.rs && \
|
||||||
|
echo "pub fn add(a: usize, b: usize) -> usize { a + b }" > calendar-models/src/lib.rs
|
||||||
|
|
||||||
|
# Build dependencies (this layer will be cached unless dependencies change)
|
||||||
|
RUN cargo build --release --target wasm32-unknown-unknown --bin calendar-app
|
||||||
|
|
||||||
|
# Copy actual source code and build the frontend application
|
||||||
|
RUN rm -rf frontend
|
||||||
|
COPY frontend ./frontend
|
||||||
|
RUN trunk build --release --config ./frontend/Trunk.toml
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Backend build stage
|
||||||
|
# -----------------------------------------------------------
|
||||||
|
FROM rust:alpine AS backend-builder
|
||||||
|
|
||||||
|
# Install build dependencies for backend
|
||||||
|
WORKDIR /app
|
||||||
|
RUN apk add --no-cache musl-dev pkgconfig openssl-dev openssl-libs-static
|
||||||
|
|
||||||
|
# Copy shared models
|
||||||
|
COPY calendar-models ./calendar-models
|
||||||
|
|
||||||
|
# Create empty frontend directory to satisfy workspace
|
||||||
|
RUN mkdir -p frontend/src && \
|
||||||
|
printf '[package]\nname = "calendar-app"\nversion = "0.1.0"\nedition = "2021"\n\n[dependencies]\n' > frontend/Cargo.toml && \
|
||||||
|
echo 'fn main() {}' > frontend/src/main.rs
|
||||||
|
|
||||||
|
# Create dummy backend source to build dependencies first
|
||||||
|
RUN mkdir -p backend/src && \
|
||||||
|
echo "fn main() {}" > backend/src/main.rs
|
||||||
|
|
||||||
|
# Build dependencies (this layer will be cached unless dependencies change)
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
COPY backend/Cargo.toml ./backend/
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
# Build the backend
|
||||||
|
COPY backend ./backend
|
||||||
|
RUN cargo build --release --bin backend
|
||||||
|
|
||||||
# Build the application
|
|
||||||
RUN trunk build --release
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Runtime stage
|
# Runtime stage
|
||||||
# ---------------------------------------
|
# -----------------------------------------------------------
|
||||||
FROM docker.io/nginx:alpine
|
FROM alpine:latest
|
||||||
|
|
||||||
# Remove default nginx content
|
# Install runtime dependencies
|
||||||
RUN rm -rf /usr/share/nginx/html/*
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
|
||||||
# Copy built application from builder stage
|
# Copy frontend files to temporary location
|
||||||
COPY --from=builder /app/dist/* /usr/share/nginx/html/
|
COPY --from=builder /app/frontend/dist /app/frontend-dist
|
||||||
|
|
||||||
# Add nginx configuration for SPA
|
# Copy backend binary (built in workspace root)
|
||||||
RUN echo 'server { \
|
COPY --from=backend-builder /app/target/release/backend /usr/local/bin/backend
|
||||||
listen 80; \
|
|
||||||
server_name localhost; \
|
|
||||||
root /usr/share/nginx/html; \
|
|
||||||
index index.html; \
|
|
||||||
location / { \
|
|
||||||
try_files $uri $uri/ /index.html; \
|
|
||||||
} \
|
|
||||||
# Enable gzip compression \
|
|
||||||
gzip on; \
|
|
||||||
gzip_types text/css application/javascript application/wasm; \
|
|
||||||
}' > /etc/nginx/conf.d/default.conf
|
|
||||||
|
|
||||||
# Expose port
|
# Create startup script to copy frontend files to shared volume
|
||||||
EXPOSE 80
|
RUN mkdir -p /srv/www
|
||||||
|
RUN echo '#!/bin/sh' > /usr/local/bin/start.sh && \
|
||||||
|
echo 'cp -r /app/frontend-dist/* /srv/www/' >> /usr/local/bin/start.sh && \
|
||||||
|
echo 'echo "Starting backend server..."' >> /usr/local/bin/start.sh && \
|
||||||
|
echo '/usr/local/bin/backend' >> /usr/local/bin/start.sh && \
|
||||||
|
chmod +x /usr/local/bin/start.sh
|
||||||
|
|
||||||
# Health check
|
# Start with script that copies frontend files then starts backend
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
CMD ["/usr/local/bin/start.sh"]
|
||||||
CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1
|
|
||||||
|
|
||||||
# Start nginx
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# Modern CalDAV Web Client
|
# Modern CalDAV Web Client
|
||||||
|
|
||||||
|
>[!WARNING]
|
||||||
|
>This project was entirely vibe coded. It's my first attempt vibe coding anything, but I just sat down one day and realized this was something I've wanted to build for many years, but would just take way too long. With AI, I've been able to lay out the majority of the app in one long weekend. So proceed at your own risk, but I actually think the app is pretty solid.
|
||||||
|
|
||||||
A full-stack calendar application built with Rust, featuring a modern web interface for CalDAV calendar management.
|
A full-stack calendar application built with Rust, featuring a modern web interface for CalDAV calendar management.
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
@@ -122,4 +125,4 @@ This client is designed to work with any RFC-compliant CalDAV server:
|
|||||||
- **Apple Calendar Server** - 🚧 Planned standards-compliant operation
|
- **Apple Calendar Server** - 🚧 Planned standards-compliant operation
|
||||||
- **Google Calendar** - 🚧 Planned CalDAV API compatibility
|
- **Google Calendar** - 🚧 Planned CalDAV API compatibility
|
||||||
|
|
||||||
*Note: While the client follows RFC standards and should work with any compliant CalDAV server, we have currently only tested extensively with Baikal. Testing with other servers is planned.*
|
*Note: While the client follows RFC standards and should work with any compliant CalDAV server, we have currently only tested extensively with Baikal. Testing with other servers is planned.*
|
||||||
|
|||||||
22
compose.yml
Normal file
22
compose.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
services:
|
||||||
|
calendar-backend:
|
||||||
|
build: .
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- ./data/site_dist:/srv/www
|
||||||
|
|
||||||
|
calendar-frontend:
|
||||||
|
image: caddy
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./data/site_dist:/srv/www:ro
|
||||||
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
- ./data/caddy/data:/data
|
||||||
|
- ./data/caddy/config:/config
|
||||||
@@ -4,7 +4,15 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Calendar App</title>
|
<title>Calendar App</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<base data-trunk-public-url />
|
||||||
<link data-trunk rel="css" href="styles.css">
|
<link data-trunk rel="css" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
<body></body>
|
<body>
|
||||||
|
<script>
|
||||||
|
console.log("HTML loaded, waiting for WASM...");
|
||||||
|
window.addEventListener('TrunkApplicationStarted', () => {
|
||||||
|
console.log("Trunk application started successfully!");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -575,6 +575,9 @@ pub fn App() -> Html {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
web_sys::console::log_1(&format!("App rendering: auth_token = {:?}", auth_token.is_some()).into());
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<div class="app" onclick={on_outside_click}>
|
<div class="app" onclick={on_outside_click}>
|
||||||
|
|||||||
Reference in New Issue
Block a user