- Path:
docs/setup/ubuntu/auth.md - Template Version:
20260508
This document describes how to configure credentials for GitHub CLI and Codex CLI when running Codex agents through GitHub Flows.
The credentials are used by Docker-isolated agent executions.
This document covers:
- GitHub personal access token storage;
- GitHub CLI authentication through
GH_TOKEN; - Codex authentication through persisted Codex auth state;
- profile configuration for passing credentials into the agent container.
This document does not describe Docker image creation, application deployment, Apache configuration, or GitHub webhook setup.
The runtime user is referenced as user.
The application is deployed into:
/home/user/app/github-flows/
The runtime workspace is located under:
/home/user/app/github-flows/var/work/
The Codex agent image is:
github-flows-agent-codex:latest
The GitHub token file is:
/home/user/.secrets/gh-token
The Codex auth state directory is:
/home/user/.secrets/codex/
Inside the container:
/run/secrets/gh-token GitHub token file
/home/user/.codex Codex auth state
/workspace per-run workspace
The GitHub account used by the agent must have access to the target repository.
For a private organization repository, organization membership alone may not be enough. The account must have repository access through direct repository permissions, team membership, or another approved organization access policy.
For a Codex agent that reads issues, modifies files, pushes branches, and opens pull requests, the GitHub account typically needs write access to the target repository.
Create a fine-grained personal access token for the GitHub account used by the agent.
Recommended repository access:
Only selected repositories:
owner/repository
Recommended repository permissions:
Metadata: Read
Contents: Read and write
Issues: Read and write
Pull requests: Read and write
If the repository belongs to an organization, the token may require organization owner approval before it can access private organization resources.
For organization approval, use the organization settings:
Organization
-> Settings
-> Personal access tokens
-> Pending requests
-> select the token request
-> Approve
Run under the runtime user:
sudo -iu userCreate the secrets directory:
mkdir -p /home/user/.secrets
chmod 700 /home/user/.secretsCreate the token file:
nano /home/user/.secrets/gh-token
chmod 600 /home/user/.secrets/gh-tokenThe file must contain only the raw GitHub token value.
Check that the runtime user can read the token:
test -r /home/user/.secrets/gh-token && echo gh-token-okCheck the GitHub identity exposed by the token:
docker run --rm \
--mount type=bind,src=/home/user/.secrets/gh-token,dst=/run/secrets/gh-token,readonly \
-e GH_TOKEN_FILE=/run/secrets/gh-token \
github-flows-agent-codex:latest \
bash -lc 'export GH_TOKEN="$(tr -d "\r\n" < "$GH_TOKEN_FILE")"; export GITHUB_TOKEN="$GH_TOKEN"; gh api user --jq .login'Check repository access:
docker run --rm \
--mount type=bind,src=/home/user/.secrets/gh-token,dst=/run/secrets/gh-token,readonly \
-e GH_TOKEN_FILE=/run/secrets/gh-token \
github-flows-agent-codex:latest \
bash -lc 'export GH_TOKEN="$(tr -d "\r\n" < "$GH_TOKEN_FILE")"; export GITHUB_TOKEN="$GH_TOKEN"; gh repo view owner/repository'The tr -d "\r\n" command removes line endings from the token file before exporting the token.
Codex CLI supports authentication through a ChatGPT account or through an OpenAI API key.
For subscription-based Codex use, authenticate Codex through ChatGPT sign-in and persist the Codex auth state outside the container image.
Create the Codex auth directory:
sudo -iu user
mkdir -p /home/user/.secrets/codex
chmod 700 /home/user/.secrets/codexRun Codex login interactively:
docker run --rm -it \
--name github-flows-codex-login \
--mount type=bind,src=/home/user/.secrets/codex,dst=/home/user/.codex \
--mount type=bind,src=/home/user/app/github-flows/var/work,dst=/workspace \
-w /workspace \
github-flows-agent-codex:latest \
codex loginComplete the browser or device authentication flow shown by Codex.
After login, check that Codex auth state was written:
find /home/user/.secrets/codex -maxdepth 3 -type f -lsRun Codex interactively with the persisted auth state:
docker run --rm -it \
--name github-flows-codex-interactive \
--mount type=bind,src=/home/user/.secrets/codex,dst=/home/user/.codex \
--mount type=bind,src=/home/user/app/github-flows/var/work,dst=/workspace \
-w /workspace \
github-flows-agent-codex:latest \
codexInside Codex, select the model:
/model
Check the active session settings:
/status
The selected model and Codex settings are stored in the mounted Codex auth/config directory.
A profile can mount both credential sources into the container:
{
"runtime": {
"env": {
"LOG_LEVEL": "info",
"GH_TOKEN_FILE": "/run/secrets/gh-token"
},
"dockerArgs": [
"--mount",
"type=bind,src=/home/user/.secrets/codex,dst=/home/user/.codex",
"--mount",
"type=bind,src=/home/user/.secrets/gh-token,dst=/run/secrets/gh-token,readonly"
]
}
}This does not automatically create GH_TOKEN.
The profile command must read GH_TOKEN_FILE and export GH_TOKEN before starting Codex.
This profile launches Codex through bash -lc, reads the GitHub token from /run/secrets/gh-token, exports GH_TOKEN and GITHUB_TOKEN, and then starts Codex.
{
"trigger": {
"repository": "owner/repository",
"event": "issue_comment",
"action": "created"
},
"execution": {
"handler": {
"type": "agent",
"command": ["bash", "-lc"],
"args": [
"export GH_TOKEN=\"$(tr -d '\\r\\n' < \"$GH_TOKEN_FILE\")\"; export GITHUB_TOKEN=\"$GH_TOKEN\"; exec codex exec --dangerously-bypass-approvals-and-sandbox -C /workspace/repo"
],
"promptRef": "prompt.md",
"promptVariables": {
"REPOSITORY": "event.repository.full_name",
"ISSUE_NUMBER": "event.issue.number",
"ISSUE_TITLE": "event.issue.title",
"ISSUE_BODY": "event.issue.body",
"ISSUE_AUTHOR": "event.issue.user.login"
}
},
"runtime": {
"image": "github-flows-agent-codex:latest",
"setupScript": "test -d repo",
"timeoutSec": 1800,
"env": {
"LOG_LEVEL": "info",
"GH_TOKEN_FILE": "/run/secrets/gh-token"
},
"dockerArgs": [
"--mount",
"type=bind,src=/home/user/.secrets/codex,dst=/home/user/.codex",
"--mount",
"type=bind,src=/home/user/.secrets/gh-token,dst=/run/secrets/gh-token,readonly"
]
}
}
}Prepare a test workspace:
mkdir -p /home/user/app/github-flows/var/work/auth-testRun a container with both GitHub and Codex credentials mounted:
docker run --rm -it \
--name github-flows-auth-test \
--mount type=bind,src=/home/user/.secrets/codex,dst=/home/user/.codex \
--mount type=bind,src=/home/user/.secrets/gh-token,dst=/run/secrets/gh-token,readonly \
--mount type=bind,src=/home/user/app/github-flows/var/work/auth-test,dst=/workspace \
-e GH_TOKEN_FILE=/run/secrets/gh-token \
-w /workspace \
github-flows-agent-codex:latest \
bash -lc 'export GH_TOKEN="$(tr -d "\r\n" < "$GH_TOKEN_FILE")"; export GITHUB_TOKEN="$GH_TOKEN"; gh api user --jq .login && codex --help'Do not mount the runtime user home directory:
/home/user
Do not mount SSH configuration:
/home/user/.ssh/
Do not mount the full user configuration directory:
/home/user/.config/
Do not mount the application .env file into the agent workspace:
/home/user/app/github-flows/.env
Do not mount the Docker socket:
/var/run/docker.sock
Do not place credentials inside:
/home/user/app/github-flows/var/work/
The runtime workspace may be visible through logs or debugging tools. It must not contain long-lived credentials.
After this setup:
- the GitHub token is stored in
/home/user/.secrets/gh-token; - the GitHub token file is readable only by the runtime user;
- the Codex auth state is stored in
/home/user/.secrets/codex/; - GitHub CLI receives
GH_TOKENandGITHUB_TOKENfrom the mounted token file; - Codex CLI uses the mounted Codex auth state from
/home/user/.codex; - profile execution can authenticate to GitHub without mounting the host home directory;
- profile execution can authenticate Codex without storing Codex credentials in the image;
- credentials are mounted into the container only at runtime;
- the per-run workspace remains separated from long-lived credentials.