# syntax=docker/dockerfile:1 # # All-in-one image: PocketBase + API Server + Web App in a single container. # # The build context MUST be the project root so this file can reach both # "API Server/" and "Web App/". Build it with: # # docker build -f "Docker AIO/Dockerfile" -t carcontrol-aio . # # Run it (all three services start together): # # docker run -d --name carcontrol -p 80:80 -p 8090:8090 \ # -e PB_ADMIN_EMAIL=admin@example.com \ # -e PB_ADMIN_PASSWORD=change-me \ # -e AUTH_SECRET=$(openssl rand -hex 32) \ # -v carcontrol_pb:/pb/pb_data \ # carcontrol-aio # # Then: web app on http://host/ and PocketBase admin on http://host:8090/_/ # --- Stage 1: build the Go API Server --------------------------------------- FROM golang:1.22-alpine AS api-build WORKDIR /src COPY ["API Server/go.mod", "./"] COPY ["API Server/go.su[m]", "./"] RUN go mod download # Only main.go + internal are needed; the panel is already built into # internal/api/dist and embedded via //go:embed. COPY ["API Server/main.go", "./"] COPY ["API Server/internal", "./internal"] RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/api-server . # --- Stage 2: build the Vue Web App ----------------------------------------- FROM node:22-alpine AS web-build WORKDIR /app COPY ["Web App/package.json", "Web App/package-lock.json", "./"] RUN npm ci COPY ["Web App/index.html", "Web App/vite.config.js", "./"] COPY ["Web App/src", "./src"] COPY ["Web App/public", "./public"] # Empty -> bundle uses same-origin "/api", proxied to the API Server by nginx. ARG VITE_API_BASE RUN npm run build # --- Stage 3: runtime (all services) ---------------------------------------- FROM alpine:latest ARG PB_VERSION="" ARG TARGETARCH="amd64" RUN apk add --no-cache ca-certificates tzdata unzip wget nginx supervisor \ && mkdir -p /run/nginx # PocketBase from the official release (pinned via PB_VERSION, else latest). WORKDIR /pb RUN set -eux; \ ver="${PB_VERSION}"; \ if [ -z "$ver" ]; then \ ver="$(wget -qO- https://api.github.com/repos/pocketbase/pocketbase/releases/latest \ | grep -o '"tag_name": *"v[^"]*"' | head -1 | sed -E 's/.*"v([^"]+)".*/\1/')"; \ fi; \ echo "Installing PocketBase v${ver} (${TARGETARCH})"; \ wget -q -O /tmp/pb.zip \ "https://github.com/pocketbase/pocketbase/releases/download/v${ver}/pocketbase_${ver}_linux_${TARGETARCH}.zip"; \ unzip /tmp/pb.zip -d /pb; \ rm /tmp/pb.zip # API Server binary + built Web App static assets. COPY --from=api-build /out/api-server /usr/local/bin/api-server COPY --from=web-build /app/dist /usr/share/nginx/html # nginx: serve the SPA and proxy /api/ to the API Server on localhost. RUN cat > /etc/nginx/http.d/default.conf <<'NGINX' server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; gzip on; gzip_types text/plain text/css application/javascript application/json image/svg+xml; gzip_min_length 1024; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; 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; } location /assets/ { expires 1y; add_header Cache-Control "public, immutable"; try_files $uri =404; } location / { try_files $uri $uri/ /index.html; } } NGINX # supervisord runs the three processes and keeps them alive. RUN cat > /etc/supervisord.conf <<'SUPERVISOR' [supervisord] nodaemon=true user=root pidfile=/run/supervisord.pid logfile=/dev/null logfile_maxbytes=0 ; PocketBase: upsert the superuser (idempotent) then serve. [program:pocketbase] directory=/pb command=/bin/sh -c '/pb/pocketbase superuser upsert "$PB_ADMIN_EMAIL" "$PB_ADMIN_PASSWORD" 2>/dev/null || true; exec /pb/pocketbase serve --http=0.0.0.0:8090' priority=10 autostart=true autorestart=true stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 ; API Server: wait for PocketBase to be healthy, then start. [program:api-server] command=/bin/sh -c 'until wget -qO- http://127.0.0.1:8090/api/health >/dev/null 2>&1; do echo "waiting for pocketbase..."; sleep 1; done; exec /usr/local/bin/api-server' priority=20 autostart=true autorestart=true stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 [program:nginx] command=/usr/sbin/nginx -g 'daemon off;' priority=30 autostart=true autorestart=true stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 SUPERVISOR # API Server config: everything is local to this container. ENV PORT=8080 \ PB_URL=http://127.0.0.1:8090 \ CORS_ORIGINS=http://localhost \ AUTH_USERS_COLLECTION=users # Required at runtime (no safe defaults): PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD, # AUTH_SECRET. Pass them with `docker run -e ...`. VOLUME /pb/pb_data # 80 = Web App, 8090 = PocketBase admin, 8080 = API Server + embedded API panel. EXPOSE 80 8090 8080 CMD ["supervisord", "-c", "/etc/supervisord.conf"]