feat(api): Docker, env loading, secrets removal, route cleanup (Phase 3 — T3.0–T3.4)#44
Conversation
T3.0: Dockerfile (golang:1.22 builder + debian:bookworm-slim runtime) with gh CLI T3.0: .dockerignore excludes .env.local, secrets, build artifacts T3.0: Taskfile docker-build and docker-run tasks T3.1: godotenv loads .env then .env.local at API startup (optional files) T3.1: .env committed with non-sensitive defaults T3.2: APIRequest no longer accepts credentials or github_token fields T3.2: handler reads creds from BAUER_CREDENTIALS_PATH env var T3.3: /api/v1/workflow renamed to /api/v1/workflows T3.3: Go 1.22 method+path routing, routes consolidated T3.4: build-api Taskfile task added
There was a problem hiding this comment.
Pull request overview
Establishes a containerized and environment-driven foundation for the Bauer API by adding Docker support, loading .env files at startup, removing secrets from the workflow request body, and consolidating the workflow route.
Changes:
- Added multi-stage Docker build + docker helper tasks, plus
.dockerignoreto reduce build context exposure. - Added
.env/.env.localloading at API startup and introduced committed non-sensitive defaults. - Updated workflow API request/handler to resolve secrets from environment and renamed the workflow route to
POST /api/v1/workflows.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| Dockerfile | Adds multi-stage build for the API image and installs runtime tools (git/curl/gh). |
| .dockerignore | Excludes local secrets and build artifacts from Docker build context. |
| Taskfile.yml | Adds docker-build/docker-run tasks for local container workflows. |
| .env | Commits non-sensitive default environment values for local/dev configuration. |
| cmd/app/main.go | Loads .env files and updates route registration for workflows. |
| internal/workflow/api.go | Removes secrets from request body and resolves token/credentials from environment. |
| go.mod | Adds godotenv dependency. |
| go.sum | Records godotenv checksums. |
| docs/implementation-log.md | Documents Phase 3 implementation details and file changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,33 @@ | |||
| # --- Build stage --- | |||
| FROM golang:1.22-bookworm AS builder | |||
| docker-run: | ||
| desc: Run the Bauer API locally in Docker (reads .env.local for secrets) | ||
| cmds: | ||
| - docker run -p 8090:8090 | ||
| --env-file .env.local | ||
| -v "${BAUER_CREDENTIALS_PATH}:/creds/service-account.json:ro" | ||
| -e BAUER_CREDENTIALS_PATH=/creds/service-account.json | ||
| bauer-api:latest |
There was a problem hiding this comment.
The docker-run task uses --env-file .env.local which makes vars available inside the container, but BAUER_CREDENTIALS_PATH in the -v mount is expanded by the host shell. The Taskfile header instructs users to set up env vars before running. Adding a dotenv directive would load secrets into all tasks (build, test, lint) which is not desirable. Current approach — documented env setup + --env-file for the container — is the right trade-off for a dev-only task.
| mux.HandleFunc("POST /api/v1/workflows", workflow.ExecuteWorkflowHandler(orch)) | ||
| slog.Info("starting server", "address", ":8090") | ||
| err = http.ListenAndServe(":8090", middleware.RequestTrace(mux)) | ||
|
|
| credentials := firstNonEmpty(os.Getenv("BAUER_CREDENTIALS_PATH"), os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")) | ||
| if credentials == "" { | ||
| writeError(w, http.StatusInternalServerError, "no credentials configured: set BAUER_CREDENTIALS_PATH") | ||
| return | ||
| } |
| // Create workflow input (request fields override env defaults) | ||
| input := WorkflowInput{ | ||
| GitHubRepo: req.GitHubRepo, | ||
| GitHubToken: req.GitHubToken, | ||
| BranchPrefix: req.BranchPrefix, | ||
| GitHubToken: token, | ||
| BranchPrefix: firstNonEmpty(req.BranchPrefix, os.Getenv("BAUER_BRANCH_PREFIX"), "bauer"), | ||
| DocID: req.DocID, | ||
| Credentials: req.Credentials, | ||
| ChunkSize: req.ChunkSize, | ||
| Credentials: credentials, | ||
| ChunkSize: firstNonZero(req.ChunkSize, 1), | ||
| PageRefresh: req.PageRefresh, | ||
| OutputDir: req.OutputDir, | ||
| Model: req.Model, | ||
| OutputDir: firstNonEmpty(os.Getenv("BAUER_OUTPUT_DIR"), "bauer-output"), | ||
| Model: firstNonEmpty(req.Model, os.Getenv("BAUER_MODEL"), "gpt-5-mini-high"), | ||
| DryRun: req.DryRun, |
| github.com/google/uuid v1.6.0 // indirect | ||
| github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect | ||
| github.com/googleapis/gax-go/v2 v2.15.0 // indirect | ||
| github.com/joho/godotenv v1.5.1 // indirect |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 10 changed files in this pull request and generated 8 comments.
Comments suppressed due to low confidence (1)
cmd/app/main.go:72
godotenv.Loaddoes not override existing environment variables, so loading.envfirst will prevent.env.localfrom overriding values set by.env. If.env.localis intended to take precedence, usegodotenv.Overload(".env.local")(or load.env.localfirst and.envsecond).
slog.Info("shutdown complete with errors")
return err
}
| @@ -0,0 +1,33 @@ | |||
| # --- Build stage --- | |||
| FROM golang:1.24-bookworm AS builder | |||
| addr := ":" + port | ||
|
|
| github.com/google/s2a-go v0.1.9 // indirect | ||
| github.com/google/uuid v1.6.0 // indirect | ||
| github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect | ||
| github.com/googleapis/gax-go/v2 v2.15.0 // indirect |
| // Bauer configuration | ||
| DocID string `json:"doc_id" binding:"required"` // Google Doc ID | ||
| Credentials string `json:"credentials" binding:"required"` // Path to service account JSON | ||
| ChunkSize int `json:"chunk_size" default:"1"` // Number of chunks | ||
| PageRefresh bool `json:"page_refresh" default:"false"` // Page refresh mode | ||
| OutputDir string `json:"output_dir" default:"bauer-output"` // Output directory | ||
| Model string `json:"model" default:"gpt-5-mini-high"` // Copilot model | ||
| DryRun bool `json:"dry_run" default:"false"` // Dry run mode | ||
|
|
||
| // Local repository path | ||
| LocalRepoPath string `json:"local_repo_path" default:"/tmp"` // Where to clone (optional) | ||
| DocID string `json:"doc_id"` // Google Doc ID | ||
| ChunkSize int `json:"chunk_size,omitempty"` // Number of chunks | ||
| PageRefresh bool `json:"page_refresh,omitempty"` // Page refresh mode | ||
| Model string `json:"model,omitempty"` // Copilot model | ||
| SummaryModel string `json:"summary_model,omitempty"` // Copilot summary model |
|
|
||
| // Create workflow input | ||
| // Create workflow input (request fields override env defaults) | ||
| input := WorkflowInput{ | ||
| GitHubRepo: req.GitHubRepo, | ||
| GitHubToken: req.GitHubToken, | ||
| BranchPrefix: req.BranchPrefix, | ||
| GitHubToken: token, | ||
| BranchPrefix: firstNonEmpty(req.BranchPrefix, os.Getenv("BAUER_BRANCH_PREFIX"), "bauer"), | ||
| DocID: req.DocID, | ||
| Credentials: req.Credentials, | ||
| ChunkSize: req.ChunkSize, | ||
| PageRefresh: req.PageRefresh, | ||
| OutputDir: req.OutputDir, | ||
| Model: req.Model, | ||
| Credentials: credentials, | ||
| ChunkSize: firstNonZero(req.ChunkSize, envInt("BAUER_CHUNK_SIZE"), 1), | ||
| PageRefresh: req.PageRefresh || envBool("BAUER_PAGE_REFRESH"), | ||
| OutputDir: firstNonEmpty(os.Getenv("BAUER_OUTPUT_DIR"), "bauer-output"), | ||
| Model: firstNonEmpty(req.Model, os.Getenv("BAUER_MODEL"), "gpt-5-mini-high"), |
| } | ||
| if req.ChunkSize == 0 { | ||
| req.ChunkSize = 1 | ||
| credentials := firstNonEmpty(os.Getenv("BAUER_CREDENTIALS_PATH"), os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")) | ||
| if credentials == "" { | ||
| writeError(w, http.StatusInternalServerError, "no credentials configured: set BAUER_CREDENTIALS_PATH or GOOGLE_APPLICATION_CREDENTIALS") |
| - docker run -p 8090:8090 | ||
| --env-file .env.local | ||
| -v "${BAUER_CREDENTIALS_PATH}:/creds/service-account.json:ro" | ||
| -e BAUER_CREDENTIALS_PATH=/creds/service-account.json | ||
| bauer-api:latest |
| **Summary:** Added Docker support, env-file loading, secrets removal from the API request body, route rename, and a build task. T3.0 introduced a multi-stage `Dockerfile` (golang:1.22 builder + debian:bookworm-slim runtime with git, curl, and the GitHub CLI installed) and a `.dockerignore` that excludes secrets, build artifacts, and the git directory; two new Taskfile tasks (`docker-build`, `docker-run`) wire the image build and local container run. T3.1 installed `github.com/joho/godotenv` and updated `cmd/app/main.go` to call `godotenv.Load` for both `.env` and `.env.local` (errors silently ignored) before calling `run()`; `.env` was replaced with a committed, non-sensitive defaults file covering port, model, chunk size, page-refresh, output directory, and branch prefix. T3.2 stripped `Credentials`, `GitHubToken`, `OutputDir`, and `LocalRepoPath` from `APIRequest`, replacing them with env-var lookups (`BAUER_CREDENTIALS_PATH`/`GOOGLE_APPLICATION_CREDENTIALS` and `github.GetGitHubToken()`) inside the handler; `firstNonEmpty` and `firstNonZero` helpers apply request-field-overrides-env semantics; `SummaryModel` was added to `APIRequest` for future use. T3.3 renamed the `/api/v1/workflow` route to `POST /api/v1/workflows` using Go 1.22 method+path routing. T3.4 was already present in the Taskfile from a prior branch (`build-api` task). | ||
|
|
||
| **Files changed:** _(to be filled by agent)_ | ||
| **Files changed:** | ||
| - `Dockerfile`: New multi-stage build — golang:1.22 builder compiles `bauer-api`; debian:bookworm-slim runtime installs git, curl, ca-certificates, and the GitHub CLI; exposes port 8090. | ||
| - `.dockerignore`: Excludes `.env.local`, `config.json`, `*.pem`, build binaries, `bauer-output/`, logs, and `.git/` from the Docker build context. |
| Credentials: credentials, | ||
| ChunkSize: firstNonZero(req.ChunkSize, envInt("BAUER_CHUNK_SIZE"), 1), | ||
| PageRefresh: req.PageRefresh || envBool("BAUER_PAGE_REFRESH"), | ||
| OutputDir: firstNonEmpty(os.Getenv("BAUER_OUTPUT_DIR"), "bauer-output"), | ||
| Model: firstNonEmpty(req.Model, os.Getenv("BAUER_MODEL"), "gpt-5-mini-high"), |
| @@ -0,0 +1,33 @@ | |||
| # --- Build stage --- | |||
| FROM golang:1.24-bookworm AS builder | |||
| .env.local | ||
| config.json | ||
| *.pem | ||
| bauer | ||
| bauer-api | ||
| bauer-output/ | ||
| bauer-log.json | ||
| bauer-doc-suggestions.json | ||
| .git/ |
| docker run -p 8090:8090 \ | ||
| --env-file .env.local \ | ||
| -v "${BAUER_CREDENTIALS_PATH}:/creds/service-account.json:ro" \ | ||
| -e BAUER_CREDENTIALS_PATH=/creds/service-account.json \ | ||
| bauer-api:latest |
Summary
Establishes the API foundation: Docker support,
.envfile loading, secrets removed from request bodies, and route consolidation.Tasks Implemented
Dockerfile(golang:1.22 builder + debian:bookworm-slim runtime with git, curl, GitHub CLI)..dockerignoreexcludes secrets and build artifacts.docker-buildanddocker-runTaskfile tasks.godotenvloads.envthen.env.localbeforerun()..envcommitted with non-sensitive defaults.GitHubToken,Credentials,OutputDir,LocalRepoPathfromAPIRequest. Handler resolves secrets from env vars only.POST /api/v1/workflowswith Go 1.22 method+path routing.build-apitask (already present from prior branch).Security
.env.localexcluded from Docker image and gitFiles Changed
Dockerfile,.dockerignore— newTaskfile.yml—docker-build,docker-run.env— committed non-sensitive defaultscmd/app/main.go— godotenv loading, route renameinternal/workflow/api.go— secrets removed fromAPIRequestgo.mod/go.sum—godotenvdependencyPart of the Bauer v2 stacked PR series (Branch 9 of 12).