Deploy Open Design with Docker, Traefik, Authentik, Gemini CLI, and Codex CLI
Run Open Design as a private AI design workspace behind Traefik and Authentik, with Gemini CLI and Codex CLI installed inside the container and persisted across rebuilds.
Open Design is a local-first design workspace that can connect to terminal AI agents. In a homelab, it fits nicely as a private operator tool: expose the web UI through Traefik, protect it with Authentik, and keep the CLI agent state in persistent Docker volumes.
This guide deploys Open Design with two AI CLIs installed inside the same container:
- Gemini CLI for Google Gemini / Gemini Code Assist workflows.
- Codex CLI for OpenAI Codex workflows.
The result is a web design workspace reachable at a private HTTPS hostname, while the application container itself stays on the Docker network and does not publish a host port.
All domains, usernames, tokens, client secrets, and account details in this post are placeholders. Replace
design.example.comwith your own hostname and never publish real OAuth codes, API keys, session files, or private infrastructure details.
What this service does
Public route:
1
https://design.example.com
Access model:
1
2
3
4
5
6
7
8
9
10
11
Browser
↓
Cloudflare DNS
↓
Traefik HTTPS router
↓
Authentik forward-auth
↓
Open Design container
↓
Gemini CLI / Codex CLI inside persistent container home
Main components:
1
Open Design web/daemon process, Docker volumes for app state and CLI home, Gemini CLI, Codex CLI, Traefik, Authentik, and optional Cloudflare DNS automation.
Network model:
1
Internet → Traefik :443 → open-design:7456 on the private Docker proxy network
Open Design should not bind a public host port. Traefik is the only public entry point.
Related documentation
This service builds on the same homelab base as the rest of the stack:
- Deploy Traefik as a Docker reverse proxy with Cloudflare DNS
- Deploy Authentik SSO with Docker and Traefik
- Deploy Cloudflare Companion for Traefik DNS automation
- Deploy Homepage Dashboard with Docker, Traefik, and Authentik
- Deploy OpenClaw Gateway with Docker, Traefik and Authentik
- Deploy ComfyUI as an AI workflow service with Docker, Traefik, and Authentik
Read the Traefik and Authentik guides first if you do not already have a shared reverse-proxy network and forward-auth middleware.
Folder layout
Create one service directory for Open Design.
1
2
sudo mkdir -p /home/ubuntu/open-design
cd /home/ubuntu/open-design
The finished folder will look like this:
1
2
3
/home/ubuntu/open-design/
├── Dockerfile
└── docker-compose.yml
Runtime state is stored in Docker named volumes, not loose host folders:
1
2
open-design_open_design_data → /app/.od
open-design_open_design_home → /home/open-design
That keeps Open Design data and CLI authentication state alive across container rebuilds.
Build a custom Open Design image
Open Design can run from its upstream image, but this deployment needs Gemini CLI and Codex CLI available inside the same runtime. Create this file:
1
/home/ubuntu/open-design/Dockerfile
Add the following Dockerfile:
1
2
3
4
5
6
7
8
9
FROM docker.io/vanjayak/open-design:latest
USER root
RUN npm install -g @google/gemini-cli @openai/codex \
&& mkdir -p /home/open-design/.gemini /home/open-design/.codex \
&& chown -R open-design:open-design /home/open-design
USER open-design
Why this matters:
- Open Design can discover the CLIs from
PATH. - CLI state lives under
/home/open-design. - The custom image is reproducible and can be rebuilt later.
Compose file
Create this file:
1
/home/ubuntu/open-design/docker-compose.yml
Use this Compose file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
services:
open-design:
build:
context: .
dockerfile: Dockerfile
image: local/open-design-agents:latest
container_name: open-design
restart: unless-stopped
environment:
NODE_ENV: production
NODE_OPTIONS: --max-old-space-size=512
OD_BIND_HOST: 0.0.0.0
OD_ALLOWED_ORIGINS: https://design.example.com
OD_PORT: 7456
OD_WEB_PORT: 7456
HOME: /home/open-design
volumes:
- open_design_data:/app/.od
- open_design_home:/home/open-design
tmpfs:
- /tmp:rw,exec,nosuid,size=512m,mode=1777
read_only: true
security_opt:
- no-new-privileges:true
mem_limit: 1g
pids_limit: 512
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.middlewares.design-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.design.entrypoints=http"
- "traefik.http.routers.design.rule=Host(`design.example.com`)"
- "traefik.http.routers.design.middlewares=design-https-redirect"
- "traefik.http.routers.design-secure.entrypoints=https"
- "traefik.http.routers.design-secure.rule=Host(`design.example.com`)"
- "traefik.http.routers.design-secure.tls=true"
- "traefik.http.routers.design-secure.tls.certresolver=cloudflare"
- "traefik.http.routers.design-secure.middlewares=authentik@docker"
- "traefik.http.routers.design-secure.service=open-design"
- "traefik.http.services.open-design.loadbalancer.server.port=7456"
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:7456/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 30s
timeout: 5s
retries: 5
start_period: 30s
volumes:
open_design_data:
open_design_home:
networks:
proxy:
external: true
Important details:
OD_ALLOWED_ORIGINSshould match the real public URL.- The service is attached only to the shared
proxynetwork. - The app listens on container port
7456. - Traefik forwards to port
7456; there is noports:block. - Authentik protects the HTTPS router using the existing
authentik@dockermiddleware.
Start the service
From the service directory, build and start Open Design:
1
2
cd /home/ubuntu/open-design
sudo docker compose up -d --build
Check the container:
1
2
sudo docker compose ps
sudo docker logs --tail=80 open-design
Check the local health endpoint inside the container:
1
sudo docker exec open-design node -e "fetch('http://127.0.0.1:7456/api/health').then(async r=>{console.log(r.status, await r.text())})"
Expected result:
1
200 {"ok":true,"version":"..."}
Authentik setup
If your Authentik deployment already exposes a reusable forward-auth middleware named authentik@docker, the Open Design router can use that directly.
For clean application naming and correct callback handling, create a dedicated Authentik application/provider for Open Design:
1
2
3
4
5
6
Application name: Design
Slug: design
External host: https://design.example.com
Provider type: Proxy Provider
Proxy mode: Forward auth / single application, depending on your Authentik layout
Cookie domain: example.com
The callback path should point back to the Open Design hostname:
1
https://design.example.com/outpost.goauthentik.io/callback?X-authentik-auth-callback=true
If your embedded outpost routes callback paths through Traefik labels, add a route like this to the Authentik server labels:
1
2
3
4
5
6
- "traefik.http.routers.authentik-design-outpost.entrypoints=https"
- "traefik.http.routers.authentik-design-outpost.rule=Host(`design.example.com`) && PathPrefix(`/outpost.goauthentik.io/`)"
- "traefik.http.routers.authentik-design-outpost.priority=100"
- "traefik.http.routers.authentik-design-outpost.tls=true"
- "traefik.http.routers.authentik-design-outpost.tls.certresolver=cloudflare"
- "traefik.http.routers.authentik-design-outpost.service=authentik"
Then recreate Authentik so Traefik sees the labels:
1
2
cd /home/ubuntu/authentik
sudo docker compose up -d server worker
DNS
Create a DNS record for the public hostname:
1
design.example.com → your reverse proxy / Cloudflare target
If you use Traefik Cloudflare Companion, the Docker labels can create the record automatically. If not, create it manually in Cloudflare.
Check DNS from a machine outside the server:
1
nslookup design.example.com 1.1.1.1
Verify Traefik can reach Open Design
From the Docker host, test the service from inside the shared network:
1
2
sudo docker run --rm --network proxy alpine:3.20 \
sh -lc 'wget -S -O- -T 10 http://open-design:7456/api/health'
Expected result:
1
2
HTTP/1.1 200 OK
{"ok":true,"version":"..."}
If this fails, Traefik will not be able to proxy the app either. Check that the Open Design container is attached to the same Docker network named in traefik.docker.network.
Verify public access
Before logging in, the public URL should redirect to Authentik:
1
curl -I https://design.example.com
Expected behavior:
1
2
HTTP/2 302
location: https://auth.example.com/application/o/authorize/...
After Authentik login, you should land back on Open Design.
Verify agent discovery
Open Design exposes an agents endpoint that should discover installed CLIs. Run this inside the container:
1
sudo docker exec open-design node -e "fetch('http://127.0.0.1:7456/api/agents').then(async r=>console.log(await r.text()))"
Look for these agents in the JSON:
1
2
Gemini CLI available: true
Codex CLI available: true
You can also check versions directly:
1
sudo docker exec open-design sh -lc 'gemini --version && codex --version'
Authenticate Gemini CLI
Gemini CLI needs a Google auth method before it can serve requests. The simplest interactive path is Google login.
Start an interactive shell command:
1
sudo docker exec -it open-design sh -lc 'GOOGLE_GENAI_USE_GCA=true gemini --skip-trust'
Follow the URL shown by the CLI, approve access in Google, and paste the authorization code back into the terminal.
After login, test non-interactive execution:
1
sudo docker exec open-design sh -lc 'GOOGLE_GENAI_USE_GCA=true gemini -p "Reply exactly OK" --output-format json --skip-trust'
Expected result includes:
1
{"response":"OK"}
The exact JSON includes extra session and usage data, so do not hard-code the full output.
Authenticate Codex CLI
Codex CLI can authenticate with ChatGPT device login.
Start the login flow:
1
sudo docker exec -it open-design sh -lc 'codex login --device-auth'
Open the device URL shown by the CLI and enter the one-time code. Do not paste device codes into public logs.
Check login status:
1
sudo docker exec open-design sh -lc 'codex login status'
Expected result:
1
Logged in using ChatGPT
Then run a simple execution probe:
1
sudo docker exec open-design sh -lc 'codex --ask-for-approval never --sandbox read-only exec --skip-git-repo-check "Reply exactly OK"'
If Codex reports a usage limit, authentication still worked; the account is just quota-blocked until the reset time or until the account is upgraded.
Keep CLI authentication persistent
The Compose file mounts this volume:
1
open_design_home → /home/open-design
That is where the CLIs keep their state:
1
2
/home/open-design/.gemini
/home/open-design/.codex
Because this is a named volume, rebuilds should not wipe authentication:
1
sudo docker compose up -d --build
Do not remove the named volume unless you intentionally want to reset the app and CLI logins:
1
sudo docker volume rm open-design_open_design_home
That command deletes saved CLI state.
Homepage dashboard entry
If you use Homepage, add Open Design to your services file.
Edit this file:
1
/home/ubuntu/homepage/config/services.yaml
Add an entry under your AI or tools group:
1
2
3
4
5
- Open Design:
icon: figma.png
href: https://design.example.com
description: Local-first design workspace with Gemini CLI and Codex CLI
siteMonitor: https://design.example.com
Restart or reload Homepage if your deployment does not auto-reload config:
1
2
cd /home/ubuntu/homepage
sudo docker compose up -d
Updating Open Design
To update the upstream Open Design image and rebuild the CLI-enabled image:
1
2
3
4
cd /home/ubuntu/open-design
sudo docker compose pull
sudo docker compose build --pull
sudo docker compose up -d
Then verify health and agents again:
1
2
sudo docker inspect -f '{{.State.Health.Status}}' open-design
sudo docker exec open-design sh -lc 'gemini --version && codex --version'
Troubleshooting
Public URL returns 404
Check whether the Traefik router exists and the hostname matches exactly:
1
sudo docker inspect open-design --format '{{json .Config.Labels}}'
Common causes:
- typo in
Host(...)label; - container not attached to the
proxynetwork; - Traefik is watching a different Docker network;
- Authentik outpost callback route is missing.
Authentik redirects to the wrong domain
Check the Authentik provider’s external host and callback URL. The callback must use the same hostname users visit:
1
https://design.example.com/outpost.goauthentik.io/callback?X-authentik-auth-callback=true
If you previously used another hostname, remove the old application/provider and old DNS record to avoid confusing login redirects.
Gemini says no auth method is configured
Set GOOGLE_GENAI_USE_GCA=true for Google account auth, or provide another supported Gemini auth method.
For the Google account flow:
1
sudo docker exec -it open-design sh -lc 'GOOGLE_GENAI_USE_GCA=true gemini --skip-trust'
Codex says no credentials were found
Run device login again:
1
sudo docker exec -it open-design sh -lc 'codex login --device-auth'
Then verify:
1
sudo docker exec open-design sh -lc 'codex login status'
Codex is logged in but cannot run
If the CLI says a usage limit has been reached, the login is valid but the account is quota-blocked. Wait for the reset time shown by Codex, switch account, or upgrade the plan.
Security notes
- Keep Open Design behind Authentik or another trusted SSO layer.
- Do not expose container port
7456directly to the internet. - Treat
/home/open-design/.geminiand/home/open-design/.codexas sensitive state. - Do not commit OAuth codes, API keys, or CLI auth files to Git.
- Use named volumes or encrypted backups if the workspace matters.
- Remove old hostnames from Traefik, Authentik, and DNS when you rename the service.
Final checklist
- DNS points
design.example.comto the reverse proxy. - Open Design container is healthy.
- Traefik routes HTTPS to
open-design:7456. - Authentik redirects unauthenticated users to login.
- Authentik callback returns to
design.example.com. /api/agentsdetects Gemini CLI and Codex CLI.- Gemini CLI returns a small test response.
- Codex CLI login status is valid.
- Homepage points to the final hostname.
- Any old hostname has been removed from DNS and Authentik.