Use Secrets in Dagger
Introduction
Dagger allows you to utilize confidential information, such as passwords, API keys, SSH keys and so on, when running your Dagger pipelines, without exposing those secrets in plaintext logs, writing them into the filesystem of containers you're building, or inserting them into cache.
This tutorial teaches you the basics of using secrets in Dagger.
Requirements
This tutorial assumes that:
- You have a Go, Python or Node.js development environment. If not, install Go, Python or Node.js.
- You have a Dagger SDK installed for one of the above languages. If not, follow the installation instructions for the Dagger Go, Python or Node.js SDK.
- You have Docker installed and running on the host system. If not, install Docker.
Create and use a secret
The Dagger API provides the following queries and fields for working with secrets:
- The
setSecret
query creates a new secret from a plaintext value. - A
Container
'swithMountedSecret()
field returns the container with the secret mounted at the named filesystem path. - A
Container
'swithSecretVariable()
field returns the container with the secret stored in the named container environment variable.
Once a secret is loaded into Dagger, it can be used in a Dagger pipeline as either a variable or a mounted file. Some Dagger SDK methods additionally accept secrets as native objects.
Let's start with a simple example of setting a secret in a Dagger pipeline and using it in a container.
The following code listing creates a Dagger secret for a GitHub personal access token and then uses the token to authorize a request to the GitHub API. To use this listing, replace the TOKEN
placeholder with your personal GitHub access token.
- Go
- Node.js
- Python
package main
import (
"context"
"fmt"
"os"
"dagger.io/dagger"
)
func main() {
// initialize Dagger client
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()
// set secret
secret := client.SetSecret("ghApiToken", "TOKEN")
// use secret in container environment
out, err := client.
Container().
From("alpine:3.17").
WithSecretVariable("GITHUB_API_TOKEN", secret).
WithExec([]string{"apk", "add", "curl"}).
WithExec([]string{"sh", "-c", `curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN"`}).
Stdout(ctx)
if err != nil {
panic(err)
}
// print result
fmt.Println(out)
}
In this code listing:
- The client's
SetSecret()
method accepts two parameters - a unique name for the secret, and the secret value - and returns a newSecret
object - The
WithSecretVariable()
method also accepts two parameters - an environment variable name and aSecret
object. It returns a container with the secret value assigned to the specified environment variable. - The environment variable can then be used in subsequent container operations - for example, in a command-line
curl
request, as shown above.
import { connect } from "@dagger.io/dagger"
// initialize Dagger client
connect(
async (client) => {
// set secret
const secret = client.setSecret("ghApiToken", "TOKEN")
// use secret in container environment
const out = await client
.container()
.from("alpine:3.17")
.withSecretVariable("GITHUB_API_TOKEN", secret)
.withExec(["apk", "add", "curl"])
.withExec([
"sh",
"-c",
`curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN"`,
])
.stdout()
// print result
console.log(out)
},
{ LogOutput: process.stderr },
)
In this code listing:
- The client's
setSecret()
method accepts two parameters - a unique name for the secret, and the secret value - and returns a newSecret
object - The
withSecretVariable()
method also accepts two parameters - an environment variable name and aSecret
object. It returns a container with the secret value assigned to the specified environment variable. - The environment variable can then be used in subsequent container operations - for example, in a command-line
curl
request, as shown above.
import sys
import anyio
import dagger
async def main():
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# set secret
secret = client.set_secret("ghApiToken", "TOKEN")
# use secret in container environment
out = await (
client.container(platform=dagger.Platform("linux/amd64"))
.from_("alpine:3.17")
.with_secret_variable("GITHUB_API_TOKEN", secret)
.with_exec(["apk", "add", "curl"])
.with_exec(
[
"sh",
"-c",
"""curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN" """,
]
)
.stdout()
)
print(out)
anyio.run(main)
In this code listing:
- The client's
set_secret()
method accepts two parameters - a unique name for the secret, and the secret value - and returns a newSecret
object - The
with_secret_variable()
method also accepts two parameters - an environment variable name and aSecret
object. It returns a container with the secret value assigned to the specified environment variable. - The environment variable can then be used in subsequent container operations - for example, in a command-line
curl
request, as shown above.
Changes in secrets do not invalidate the Dagger cache.
Use secrets from the host environment
Most of the time, it's neither practical nor secure to define plaintext secrets directly in your pipeline. That's why Dagger lets you read secrets from the host, either from host environment variables or from the host filesystem, or from external providers.
Here's a revision of the previous example, where the secret is read from a host environment variable. To use this listing, create a host environment variable named GH_SECRET
and assign it the value of your GitHub personal access token.
- Go
- Node.js
- Python
package main
import (
"context"
"fmt"
"os"
"dagger.io/dagger"
)
func main() {
// initialize Dagger client
ctx := context.Background()
if os.Getenv("GH_SECRET") == "" {
panic("Environment variable GH_SECRET is not set")
}
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()
// read secret from host variable
secret := client.SetSecret("gh-secret", os.Getenv("GH_SECRET"))
// use secret in container environment
out, err := client.
Container().
From("alpine:3.17").
WithSecretVariable("GITHUB_API_TOKEN", secret).
WithExec([]string{"apk", "add", "curl"}).
WithExec([]string{"sh", "-c", `curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN"`}).
Stdout(ctx)
if err != nil {
panic(err)
}
fmt.Println(out)
}
This code listing assumes the existence of a host environment variable named GH_SECRET
containing the secret value. It performs the following operations:
- It reads the value of the host environment variable using the
os.Getenv()
function. - It creates a Dagger secret with that value using the
SetSecret()
method. - It uses the
WithSecretVariable()
method to return a container with the secret value assigned to an environment variable in the container. - It uses the secret in subsequent container operations, as explained previously.
import { connect } from "@dagger.io/dagger"
// check for required environment variable
if (!process.env["GH_SECRET"]) {
console.log(`GH_SECRET variable must be set`)
process.exit()
}
// initialize Dagger client
connect(
async (client) => {
// read secret from host variable
const secret = client.setSecret("gh-secret", process.env["GH_SECRET"])
// use secret in container environment
const out = await client
.container()
.from("alpine:3.17")
.withSecretVariable("GITHUB_API_TOKEN", secret)
.withExec(["apk", "add", "curl"])
.withExec([
"sh",
"-c",
`curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN"`,
])
.stdout()
// print result
console.log(out)
},
{ LogOutput: process.stderr },
)
This code listing assumes the existence of a host environment variable named GH_SECRET
containing the secret value. It performs the following operations:
- It reads the value of the host environment variable using the
process.env
object. - It creates a Dagger secret with that value using the
setSecret()
method. - It uses the
withSecretVariable()
method to return a container with the secret value assigned to an environment variable in the container. - The environment variable can then be used in subsequent container operations, as explained previously.
import os
import sys
import anyio
import dagger
async def main():
if "GH_SECRET" not in os.environ:
msg = "GH_SECRET environment variable must be set"
raise OSError(msg)
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# read secret from host variable
secret = client.set_secret("gh-secret", os.environ["GH_SECRET"])
# use secret in container environment
out = await (
client.container(platform=dagger.Platform("linux/amd64"))
.from_("alpine:3.17")
.with_secret_variable("GITHUB_API_TOKEN", secret)
.with_exec(["apk", "add", "curl"])
.with_exec(
[
"sh",
"-c",
"""curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN" """,
]
)
.stdout()
)
print(out)
anyio.run(main)
This code listing assumes the existence of a host environment variable named GH_SECRET
containing the secret value. It performs the following operations:
- It reads the value of the host environment variable using the
os.environ
object. - It creates a Dagger secret with that value using the
set_secret()
method. - It uses the
with_secret_variable()
method to return a container with the secret value assigned to an environment variable in the container. - The environment variable can then be used in subsequent container operations, as explained previously.
Use secrets from the host filesystem
Dagger also lets you mount secrets as files within a container's filesystem. This is useful for tools that look for secrets in a specific filesystem location - for example, GPG keyrings, SSH keys or file-based authentication tokens.
As an example, consider the GitHub CLI, which reads its authentication token from a file stored in the user's home directory at ~/.config/gh/hosts.yml
. The following code listing demonstrates how to mount this file at a specific location in a container as a secret, so that the GitHub CLI is able to find it.
- Go
- Node.js
- Python
package main
import (
"context"
"fmt"
"os"
"dagger.io/dagger"
)
func main() {
// initialize Dagger client
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()
// read file
config, err := os.ReadFile("/home/USER/.config/gh/hosts.yml")
if err != nil {
panic(err)
}
// set secret to file contents
secret := client.SetSecret("ghConfig", string(config))
// mount secret as file in container
out, err := client.
Container().
From("alpine:3.17").
WithExec([]string{"apk", "add", "github-cli"}).
WithMountedSecret("/root/.config/gh/hosts.yml", secret).
WithWorkdir("/root").
WithExec([]string{"gh", "auth", "status"}).
Stdout(ctx)
if err != nil {
panic(err)
}
fmt.Println(out)
}
This code listing assumes the existence of a GitHub CLI configuration file containing an authentication token at /home/USER/.config/gh/hosts.yml
. It performs the following operations:
- It reads the host file's contents into a string and loads the string into a secret with the Dagger client's
SetSecret()
method. This method returns a newSecret
object. - It uses the
WithMountedSecret()
method to return a container with the secret mounted as a file at the given location. - The GitHub CLI reads this file as needed to perform requested operations. For example, executing the
gh auth status
command in the Dagger pipeline after mounting the secret returns a message indicating that the user is logged-in, testifying to the success of the secret mount.
import { connect } from "@dagger.io/dagger"
import { readFile } from "fs/promises"
// initialize Dagger client
connect(
async (client) => {
// read file
const config = await readFile("/home/USER/.config/gh/hosts.yml")
// set secret to file contents
const secret = client.setSecret("ghConfig", config.toString())
// mount secret as file in container
const out = await client
.container()
.from("alpine:3.17")
.withExec(["apk", "add", "github-cli"])
.withMountedSecret("/root/.config/gh/hosts.yml", secret)
.withWorkdir("/root")
.withExec(["gh", "auth", "status"])
.stdout()
// print result
console.log(out)
},
{ LogOutput: process.stderr },
)
This code listing assumes the existence of a GitHub CLI configuration file containing an authentication token at /home/USER/.config/gh/hosts.yml
. It performs the following operations:
- It reads the host file's contents into a string and loads the string into a secret with the Dagger client's
setSecret()
method. This method returns a newSecret
object. - It uses the
withMountedSecret()
method to return a container with the secret mounted as a file at the given location. - The GitHub CLI reads this file as needed to perform requested operations. For example, executing the
gh auth status
command in the Dagger pipeline after mounting the secret returns a message indicating that the user is logged-in, testifying to the success of the secret mount.
import sys
import anyio
import dagger
async def main():
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# read file
config = await anyio.Path("/home/USER/.config/gh/hosts.yml").read_text()
# set secret to file contents
secret = client.set_secret("ghConfig", config)
# mount secret as file in container
out = await (
client.container(platform=dagger.Platform("linux/amd64"))
.from_("alpine:3.17")
.with_exec(["apk", "add", "github-cli"])
.with_mounted_secret("/root/.config/gh/hosts.yml", secret)
.with_workdir("/root")
.with_exec(["gh", "auth", "status"])
.stdout()
)
print(out)
anyio.run(main)
This code listing assumes the existence of a GitHub CLI configuration file containing an authentication token at /home/USER/.config/gh/hosts.yml
. It performs the following operations:
- It reads the host file's contents into a string and loads the string into a secret with the Dagger client's
set_secret()
method. This method returns a newSecret
object. - It uses the
with_mounted_secret()
method to return a container with the secret mounted as a file at the given location. - The GitHub CLI reads this file as needed to perform requested operations. For example, executing the
gh auth status
command in the Dagger pipeline after mounting the secret returns a message indicating that the user is logged-in, testifying to the success of the secret mount.
Use secrets from an external secret manager
It's also possible to read secrets into Dagger from external secret managers. The following code listing provides an example of using a secret from Google Cloud Secret Manager in a Dagger pipeline.
- Go
- Node.js
- Python
package main
import (
"context"
"fmt"
"os"
secretmanager "cloud.google.com/go/secretmanager/apiv1"
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
"dagger.io/dagger"
)
func main() {
ctx := context.Background()
// get secret from Google Cloud Secret Manager
secretPlaintext, err := gcpGetSecretPlaintext(ctx, "PROJECT-ID", "SECRET-ID")
if err != nil {
panic(err)
}
// initialize Dagger client
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()
// load secret into Dagger
secret := client.SetSecret("ghApiToken", string(secretPlaintext))
// use secret in container environment
out, err := client.
Container().
From("alpine:3.17").
WithSecretVariable("GITHUB_API_TOKEN", secret).
WithExec([]string{"apk", "add", "curl"}).
WithExec([]string{"sh", "-c", `curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN"`}).
Stdout(ctx)
if err != nil {
panic(err)
}
// print result
fmt.Println(out)
}
func gcpGetSecretPlaintext(ctx context.Context, projectID, secretID string) (string, error) {
secretUri := fmt.Sprintf("projects/%s/secrets/%s/versions/latest", projectID, secretID)
// initialize Google Cloud API client
gcpClient, err := secretmanager.NewClient(ctx)
if err != nil {
panic(err)
}
defer gcpClient.Close()
// retrieve secret
secReq := &secretmanagerpb.AccessSecretVersionRequest{
Name: secretUri,
}
res, err := gcpClient.AccessSecretVersion(ctx, secReq)
if err != nil {
panic(err)
}
secretPlaintext := res.Payload.Data
return string(secretPlaintext), nil
}
This code listing requires the user to replace the PROJECT-ID
and SECRET-ID
placeholders with corresponding Google Cloud project and secret identifiers. It performs the following operations:
- It imports the Dagger and Google Cloud Secret Manager client libraries.
- It uses the Google Cloud Secret Manager client to access and read the specified secret's payload.
- It creates a Dagger secret with that payload using the
SetSecret()
method. - It uses the
WithSecretVariable()
method to return a container with the secret value assigned to an environment variable in the container. - It uses the secret to make an authenticated request to the GitHub API, as explained previously.
import { connect } from "@dagger.io/dagger"
import { SecretManagerServiceClient } from "@google-cloud/secret-manager"
// initialize Dagger client
connect(
async (client) => {
// get secret from Google Cloud Secret Manager
const secretPlaintext = await gcpGetSecretPlaintext(
"PROJECT-ID",
"SECRET-ID",
)
// load secret into Dagger
const secret = client.setSecret("ghApiToken", secretPlaintext)
// use secret in container environment
const out = await client
.container()
.from("alpine:3.17")
.withSecretVariable("GITHUB_API_TOKEN", secret)
.withExec(["apk", "add", "curl"])
.withExec([
"sh",
"-c",
`curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN"`,
])
.stdout()
// print result
console.log(out)
},
{ LogOutput: process.stderr },
)
async function gcpGetSecretPlaintext(projectID, secretID) {
// initialize Google Cloud API client
const client = new SecretManagerServiceClient()
const secretUri = `projects/${projectID}/secrets/${secretID}/versions/latest`
// retrieve secret
const [accessResponse] = await client.accessSecretVersion({
name: secretUri,
})
const secretPlaintext = accessResponse.payload.data.toString("utf8")
return secretPlaintext
}
This code listing requires the user to replace the PROJECT-ID
and SECRET-ID
placeholders with corresponding Google Cloud project and secret identifiers. It performs the following operations:
- It imports the Dagger and Google Cloud Secret Manager client libraries.
- It uses the Google Cloud Secret Manager client to access and read the specified secret's payload.
- It creates a Dagger secret with that payload using the
setSecret()
method. - It uses the
withSecretVariable()
method to return a container with the secret value assigned to an environment variable in the container. - It uses the secret to make an authenticated request to the GitHub API, as explained previously.
import sys
import anyio
from google.cloud import secretmanager
import dagger
async def main():
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# get secret from Google Cloud Secret Manager
secret_plaintext = await gcp_get_secret_plaintext("PROJECT-ID", "SECRET-ID")
# read secret from host variable
secret = client.set_secret("ghApiToken", secret_plaintext)
# use secret in container environment
out = await (
client.container(platform=dagger.Platform("linux/amd64"))
.from_("alpine:3.17")
.with_secret_variable("GITHUB_API_TOKEN", secret)
.with_exec(["apk", "add", "curl"])
.with_exec(
[
"sh",
"-c",
"""curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN" """,
]
)
.stdout()
)
print(out)
async def gcp_get_secret_plaintext(project_id, secret_id):
secret_uri = f"projects/{project_id}/secrets/{secret_id}/versions/latest"
# initialize Google Cloud API client
client = secretmanager.SecretManagerServiceAsyncClient()
# retrieve secret
response = await client.access_secret_version(request={"name": secret_uri})
return response.payload.data.decode("UTF-8")
anyio.run(main)
This code listing requires the user to replace the PROJECT-ID
and SECRET-ID
placeholders with corresponding Google Cloud project and secret identifiers. It performs the following operations:
- It imports the Dagger and Google Cloud Secret Manager client libraries.
- It uses the Google Cloud Secret Manager client to access and read the specified secret's payload.
- It creates a Dagger secret with that payload using the
set_secret()
method. - It uses the
with_secret_variable()
method to return a container with the secret value assigned to an environment variable in the container. - It uses the secret to make an authenticated request to the GitHub API, as explained previously.
Use secrets with Dagger SDK methods
Secrets can also be used natively as inputs to some Dagger SDK methods. Here's an example, which demonstrates logging in to Docker Hub from a Dagger pipeline and publishing a new image. To use this listing, replace the DOCKER-HUB-USERNAME
and DOCKER-HUB-PASSWORD
placeholders with your Docker Hub username and password respectively.
- Go
- Node.js
- Python
package main
import (
"context"
"fmt"
"os"
"dagger.io/dagger"
)
func main() {
// initialize Dagger client
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()
// set secret as string value
secret := client.SetSecret("password", "DOCKER-HUB-PASSWORD")
// create container
c := client.Container(dagger.ContainerOpts{Platform: "linux/amd64"}).
From("nginx:1.23-alpine").
WithNewFile("/usr/share/nginx/html/index.html", dagger.ContainerWithNewFileOpts{
Contents: "Hello from Dagger!",
Permissions: 0o400,
})
// use secret for registry authentication
addr, err := c.
WithRegistryAuth("docker.io", "DOCKER-HUB-USERNAME", secret).
Publish(ctx, "DOCKER-HUB-USERNAME/my-nginx")
if err != nil {
panic(err)
}
// print result
fmt.Println("Published at:", addr)
}
In this code listing:
- The client's
SetSecret()
method returns a newSecret
representing the Docker Hub password. - The
WithRegistryAuth()
method accepts three parameters - the Docker Hub registry address, the username and the password (as aSecret
) - and returns a container pre-authenticated for the registry. - The
Publish()
method publishes the container to Docker Hub.
import { connect } from "@dagger.io/dagger"
// initialize Dagger client
connect(
async (client) => {
// set secret as string value
const secret = client.setSecret("password", "DOCKER-HUB-PASSWORD")
// create container
const c = client
.container()
.from("nginx:1.23-alpine")
.withNewFile("/usr/share/nginx/html/index.html", {
contents: "Hello from Dagger!",
permissions: 0o400,
})
// use secret for registry authentication
const addr = await c
.withRegistryAuth("docker.io", "DOCKER-HUB-USERNAME", secret)
.publish("DOCKER-HUB-USERNAME/my-nginx")
// print result
console.log(`Published at: ${addr}`)
},
{ LogOutput: process.stderr },
)
In this code listing:
- The client's
setSecret()
method returns a newSecret
representing the Docker Hub password. - The
withRegistryAuth()
method accepts three parameters - the Docker Hub registry address, the username and the password (as aSecret
) - and returns a container pre-authenticated for the registry. - The
publish()
method publishes the container to Docker Hub.
import sys
import anyio
import dagger
async def main():
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# set secret as string value
secret = client.set_secret("password", "DOCKER-HUB-PASSWORD")
# create container
ctr = (
client.container(platform=dagger.Platform("linux/amd64"))
.from_("nginx:1.23-alpine")
.with_new_file(
"/usr/share/nginx/html/index.html",
contents="Hello from Dagger!",
permissions=0o400,
)
)
# use secret for registry authentication
addr = await ctr.with_registry_auth(
"docker.io", "DOCKER-HUB-USERNAME", secret
).publish("DOCKER-HUB-USERNAME/my-nginx")
print(f"Published at: {addr}")
anyio.run(main)
In this code listing:
- The client's
set_secret()
method returns a newSecret
representing the Docker Hub password. - The
with_registry_auth()
method accepts three parameters - the Docker Hub registry address, the username and the password (as aSecret
) - and returns a container pre-authenticated for the registry. - The
publish()
method publishes the container to Docker Hub.
Use secrets with Dockerfile builds
Secrets can also be passed to Dockerfile builds performed with Dagger. Build secrets set with Dagger are automatically mounted in the build container at the default Dockerfile location of /run/secrets/SECRET-ID
.
Here's an example, which demonstrates setting a build secret in a Dagger pipeline and using that secret in a Dockerfile build:
- Go
- Node.js
- Python
package main
import (
"context"
"fmt"
"os"
"dagger.io/dagger"
)
func main() {
ctx := context.Background()
if os.Getenv("GH_SECRET") == "" {
panic("Environment variable GH_SECRET is not set")
}
// initialize Dagger client
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()
// read secret from host variable
secret := client.SetSecret("gh-secret", os.Getenv("GH_SECRET"))
// set context directory for Dockerfile build
contextDir := client.Host().Directory(".")
// build using Dockerfile
// specify secrets for Dockerfile build
// secrets will be mounted at /run/secrets/[secret-name]
out, err := contextDir.
DockerBuild(dagger.DirectoryDockerBuildOpts{
Dockerfile: "Dockerfile",
Secrets: []*dagger.Secret{secret},
}).
Stdout(ctx)
if err != nil {
panic(err)
}
fmt.Println(out)
}
This code listing expects a host environment variable named GH_SECRET
containing the secret value. It performs the following operations:
- It reads the value of the host environment variable using the
os.Getenv()
function. - It creates a Dagger secret named
gh-secret
with that value using theSetSecret()
function. - It uses the
DockerBuild()
function to build an image from a Dockerfile. The secret is automatically mounted in the build container at/run/secrets/gh-secret
.
import { connect } from "@dagger.io/dagger"
// check for required environment variable
if (!process.env["GH_SECRET"]) {
console.log(`GH_SECRET variable must be set`)
process.exit()
}
// initialize Dagger client
connect(
async (client) => {
// read secret from host variable
const secret = client.setSecret("gh-secret", process.env["GH_SECRET"])
// set context directory for Dockerfile build
const contextDir = client.host().directory(".")
// build using Dockerfile
// specify secrets for Dockerfile build
// secrets will be mounted at /run/secrets/[secret-name]
const out = await contextDir
.dockerBuild({
dockerfile: "Dockerfile",
secrets: [secret],
})
.stdout()
// print result
console.log(out)
},
{ LogOutput: process.stderr },
)
This code listing expects a host environment variable named GH_SECRET
containing the secret value. It performs the following operations:
- It reads the value of the host environment variable using the
process.env
object. - It creates a Dagger secret named
gh-secret
with that value using thesetSecret()
function. - It uses the
dockerBuild()
function to build an image from a Dockerfile. The secret is automatically mounted in the build container at/run/secrets/gh-secret
.
import os
import sys
import anyio
import dagger
async def main():
if "GH_SECRET" not in os.environ:
msg = "GH_SECRET environment variable must be set"
raise OSError(msg)
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# read secret from host variable
secret = client.set_secret("gh-secret", os.environ["GH_SECRET"])
# set context directory for Dockerfile build
context_dir = client.host().directory(".")
# build using Dockerfile
# specify secrets for Dockerfile build
# secrets will be mounted at /run/secrets/[secret-name]
out = await context_dir.docker_build(
dockerfile="Dockerfile",
secrets=[secret],
).stdout()
print(out)
anyio.run(main)
This code listing expects a host environment variable named GH_SECRET
containing the secret value. It performs the following operations:
- It reads the value of the host environment variable using the
os.environ
object. - It creates a Dagger secret named
gh-secret
with that value using theset_secret()
function. - It uses the
docker_build()
function to build an image from a Dockerfile. The secret is automatically mounted in the build container at/run/secrets/gh-secret
.
The sample Dockerfile below demonstrates the process of mounting the secret using a secret
filesystem mount type and using it in the Dockerfile build process:
FROM alpine:3.17
RUN apk add curl
RUN --mount=type=secret,id=gh-secret curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $(cat /run/secrets/gh-secret)"
Understand how Dagger secures secrets
Dagger automatically scrubs secrets from its various logs and output streams. This ensures that sensitive data does not leak - for example, in the event of a crash. This applies to secrets stored in both environment variables and file mounts.
The following example demonstrates this feature:
- Go
- Node.js
- Python
package main
import (
"context"
"fmt"
"os"
"dagger.io/dagger"
)
func main() {
ctx := context.Background()
// create Dagger client
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()
secretEnv := client.SetSecret("my-secret-var", "secret value here")
secretFile := client.SetSecret("my-secret-file", "secret file content here")
// dump secrets to console
out, err := client.Container().
From("alpine:3.17").
WithSecretVariable("MY_SECRET_VAR", secretEnv).
WithMountedSecret("/my_secret_file", secretFile).
WithExec([]string{"sh", "-c", `echo -e "secret env data: $MY_SECRET_VAR || secret file data: "; cat /my_secret_file`}).
Stdout(ctx)
if err != nil {
panic(err)
}
fmt.Println(out)
}
import { connect } from "@dagger.io/dagger"
// initialize Dagger client
connect(
async (client) => {
const secretEnv = client.setSecret("my-secret-env", "secret value here")
const secretFile = client.setSecret(
"my-secret-file",
"secret file content here",
)
// dump secrets to console
const out = await client
.container()
.from("alpine:3.17")
.withSecretVariable("MY_SECRET_VAR", secretEnv)
.withMountedSecret("/my_secret_file", secretFile)
.withExec([
"sh",
"-c",
`echo -e "secret env data: $MY_SECRET_VAR || secret file data: "; cat /my_secret_file`,
])
.stdout()
console.log(out)
},
{ LogOutput: process.stderr },
)
import sys
import anyio
import dagger
async def main():
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
secret_env = client.set_secret("my-secret-var", "secret value here")
secret_file = client.set_secret("my-secret-file", "secret file content here")
# dump secrets to console
out = await (
client.container()
.from_("alpine:3.17")
.with_secret_variable("MY_SECRET_VAR", secret_env)
.with_mounted_secret("/my_secret_file", secret_file)
.with_exec(
[
"sh",
"-c",
""" echo -e "secret env data: $MY_SECRET_VAR || secret file data: "; cat /my_secret_file """,
]
)
.stdout()
)
print(out)
anyio.run(main)
This listing creates dummy secrets on the host (as an environment variable and a file), loads them into Dagger and then attempts to print them to the console. However, Dagger automatically scrubs the sensitive data before printing it, as shown in the output below:
secret env data: *** || secret file data:
***
Any secret that is to be read from the container environment should always be loaded using withSecretVariable()
. If withEnvVariable()
is used instead, the value of the environment variable may leak via the build history of the container. Using withSecretVariable()
guarantees that the secret will not leak in the container build history or image layers.
Conclusion
This tutorial walked you through the basics of using secrets in Dagger. It explained the various API methods available to work with secrets and provided examples of using secrets as environment variables, file mounts and native objects.
Use the API Key Concepts page and the Go, Node.js and Python SDK References to learn more about Dagger.