Ever since I was at Search Discovery I’d been trying to figure out a way to add basic authentication to mkdocs. This proved to be way more challenging than I thought it might be.
At one point I considered just signing up for ReadTheDocs or Gitbook, but I persisted. What I ended up with may be over-engineered, but it works.
What I tried
Asked ChatGPT what it would do
Recommended deploying via VM instance or Caddy (frontend proxy)
Googled around
Found an article that walked through deploying from GitLab CI
What worked
Building as a Cloud Run service, deploying an nginx frontend proxy as the “ingress” container (one that handles incoming requests), then the main mkdocs container as a “sidecar”
Cloud Run service
Cloud Run has several advantages over Compute Engine VM instances:
- Cheaper — because the site will get so little traffic, it makes sense to only spin it up when it’s needed. VM instances are always on, Cloud Run services are not
- Easier — rather than trying to build your own CI/CD pipeline from scratch, Cloud Run lets you compose
Suppose you could also use App Engine
Build docs container using Cloud Run CD to match the platform architecture and avoid “exec format error”
Containers
- An nginx frontend proxy serving on port 80, proxying traffic to/from port 8080
- The mkdocs container serving on port 8080
Secrets
You can’t edit nginx.conf directly but you can add an nginx.conf file as a secret using Secrets Manager and mount it as a volume
Deployment
- Working YAML
apiVersion: serving.knative.dev/v1 kind: Service metadata: name: mkdocs-service namespace: '194919427391' selfLink: /apis/serving.knative.dev/v1/namespaces/153919444396/services/mkdocs-service uid: 393f453a-6a41-40ab-9814-8633d2950f00 resourceVersion: AAYY9UUGwkY generation: 10 creationTimestamp: '2024-05-21T10:20:53.007737Z' labels: cloud.googleapis.com/location: us-central1 annotations: run.googleapis.com/client-name: cloud-console serving.knative.dev/creator: [email protected] serving.knative.dev/lastModifier: [email protected] run.googleapis.com/description: MkDocs site run.googleapis.com/operation-id: 4ede382b-2808-4f84-a35f-1004761f6bd7 run.googleapis.com/ingress: all run.googleapis.com/ingress-status: all spec: template: metadata: labels: client.knative.dev/nonce: 0ec7f9fb-87aa-4a20-8623-6d9eed070849 run.googleapis.com/startupProbeType: Default annotations: run.googleapis.com/client-name: cloud-console autoscaling.knative.dev/maxScale: '5' run.googleapis.com/startup-cpu-boost: 'true' run.googleapis.com/container-dependencies: '{"nginx":["mkdocs-site"]}' spec: containerConcurrency: 80 timeoutSeconds: 300 serviceAccountName: [email protected] containers: - name: nginx image: nginx ports: - name: http1 containerPort: 80 resources: limits: cpu: 500m memory: 256Mi volumeMounts: - name: nginx-conf-secret readOnly: true mountPath: /etc/nginx/conf.d/ - name: nginx-htpasswd mountPath: /etc/nginx startupProbe: timeoutSeconds: 240 periodSeconds: 240 failureThreshold: 1 tcpSocket: port: 80 - name: mkdocs-site image: gcr.io/acme-explosives-423614/github.com/somedomain/client-data-layer-spec@sha256:425fa276d0fc9df49c9b727785a27ed3373952e51e5792b6a68dca821e85b289 env: - name: PORT value: '8080' resources: limits: cpu: 1000m memory: 512Mi startupProbe: timeoutSeconds: 240 periodSeconds: 240 failureThreshold: 1 tcpSocket: port: 8080 volumes: - name: nginx-conf-secret secret: secretName: nginx_config items: - key: latest path: default.conf - name: nginx-htpasswd secret: secretName: nginx_htpasswd items: - key: latest path: .htpasswd traffic: - percent: 100 latestRevision: true status: observedGeneration: 10 conditions: - type: Ready status: 'True' lastTransitionTime: '2024-05-21T11:39:24.388934Z' - type: ConfigurationsReady status: 'True' lastTransitionTime: '2024-05-21T10:20:53.131621Z' - type: RoutesReady status: 'True' lastTransitionTime: '2024-05-21T11:39:24.347405Z' latestReadyRevisionName: mkdocs-service-00005-4nj latestCreatedRevisionName: mkdocs-service-00005-4nj traffic: - revisionName: mkdocs-service-00005-4nj percent: 100 latestRevision: true url: https://mkdocs-service-vwuoluxl6a-uc.a.run.app address: url: https://mkdocs-service-vwuoluxl6a-uc.a.run.app
Deploy using CLI
gcloud run services replace service.yamlDocs site must have Dockerfile. This worked:
FROM python:3.9-slim
# git required for mkdocs
RUN apt-get update && apt-get install -y git
# install mkdocs-material and plugins
RUN pip install mkdocs mkdocs-material mkdocs-git-revision-date-localized-plugin
COPY . .
EXPOSE 8080
CMD ["mkdocs","serve"]Challenges
Permissions
Building the container
Cloud Run
Next steps
Auth using Google account
Right now using nginx basic auth. Should be fine for all users to share a password. Might be cool/more professional if they could log in via Google oauth using e.g. oauth2-proxy
Custom domain
https://console.cloud.google.com/run/domains?project=acme-explosives-423614
You can add a custom domain so you don’t have to hand off an ugly URL like https://mkdocs-service-vwuoluxl6a-uc.a.run.app/ to the client
Optimize build
Currently runs using mkdocs serve, which is intended for development (python server, includes live reloading, etc.) — maybe use mkdocs build in the dockerfile and then serve using nginx or caddy?