Podman secrets: a better way to pass environment variables to containers

Podman became the standard container runtime with Oracle Linux 8 and later, and I have become a fan almost instantly. I especially like the fact that it is possible to run containers with far fewer privileges than previously required. Most Podman containers can be started by regular users, a fact I greatly appreciate in my development environments.

A common technique of passing information to containers is to use the –env command line argument when invoking podman run. Since passing sensitive information on the command line is never a good idea I searched for alternatives for a local development environment. Podman secrets are a very promising approach although not without their own issues.

Before you consider using them please ensure your security department signs their use off, as always. They are pretty easy to translate back into plain text if you have access to the container host! If you need to encrypt a secret, your container must be able to deal with that – see https://container-registry.oracle.com/ords/ocr/ba/database/free for an example.

This post describes the common scenario of instantiating container instances with host-based secrets as you find them in developer environments. It specifically does not apply to orchestration frameworks such as Kubernetes.

Podman Secrets

Podman secrets provide an alternative way for passing environment variables to containers. According to the documentation,

A secret is a blob of sensitive data which a container needs at runtime but should not be stored in the image or in source control, such as usernames and passwords, TLS certificates and keys, SSH keys or other important generic strings or binary content (up to 500 kb in size).

Why is this a big deal for me? You frequently find instructions in blog posts and other sources how to pass information to containers, such as user names and passwords. For example:

$ podman run --rm -it --name some-container \
-e USERNAME=someUsername \
-e PASSWORD=thisShouldNotBeEnteredHere \
docker.io/some/image:some-tag

Whilst specifying usernames and passwords on the command line is convenient for testing, passing credentials this way is not exactly secure. Quite the contrary. Plus there is a risk these things make it into a source code control system and thus become publicly available.

Podman Secrets in Action

Secrets are separate entities, you create them using podman secret create (documentation link). They work quite well, provided they do not contain newlines, which is easy to overlook in the docs.

Let’s see how you can make these secrets work with Gerald Venzl’s image. According to its documentation you can use secrets for both the administration account as well as the application user. Two secrets have been created beforehand:

  • oracle-passwd contains the password for the image’s privileged accounts like SYS and SYSTEM. You don’t use these accounts for your application
  • app-passwd will be used to create the application user. The latter is home to my sample application

After the secrets have been created there are two options to create the container instance on your laptop:

  1. using podman run
  2. based on a compose file (and implicitly, podman-compose)

This post was written with the following software stack:

  • Linux Mint 22
  • Podman 4.9.3 (as per the distribution)
  • Podman Compose 1.4.0 (installed via PIP into a virtual environment)

Let’s see how secrets can be used in stand-alone Podman before looking at podman-compose.

Podman run

Use podman run to create a new container from scratch. Gerald Venzl’s image comes with extensive documentation making it easy to pick the right options. Here’s an example, you might need different options:

podman run --detach \
--volume some-oracle-data-vol:/opt/oracle/oradata \
--name some-oracle \
--publish 1521:1521 \
--secret oracle-passwd,type=env,target=ORACLE_PASSWORD \
--env APP_USER=demouser \
--secret app-passwd,type=env,target=APP_USER_PASSWORD \
docker.io/gvenzl/oracle-free:23.8

The above command creates a new container instance, deploys the database into some-oracle-data-vol (so it survives problems with the container) and publishes the listener port to port 1521 on the host. It also initialises the DBA accounts and the APP_USER with the secrets provided.

Connection Test

Let’s test the connection against the newly created database using SQLcl:

$ sql demouser@localhost/freepdb1 


SQLcl: Release 25.1 Production on Wed Jul 02 09:29:24 2025

Copyright (c) 1982, 2025, Oracle. All rights reserved.

Password? (**********?)
Connected to:
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.8.0.25.04

SQL> select
2 *
3 from
4* product_component_version;

PRODUCT VERSION VERSION_FULL STATUS
____________________________ _____________ _______________ ___________________________________
Oracle Database 23ai Free 23.0.0.0.0 23.8.0.25.04 Develop, Learn, and Run for Free

SQL> show user
USER is "DEMOUSER"

Tada – it worked ;)

Podman Compose

Using a podman-compose is another way of creating a database. It’s more convenient, too, since it’s easy to bring up multiple services, and tearing down an environment is just a one-liner, too. Here’s the compose file I use a lot (other services removed for brevity)

services:
oracle:
image: docker.io/gvenzl/oracle-free:23.8
ports:
- 1521:1521
environment:
- ORACLE_PASSWORD_FILE=/run/secrets/oracle-passwd
- APP_USER=demouser
- APP_USER_PASSWORD_FILE=/run/secrets/app-passwd
volumes:
- oradata-vol:/opt/oracle/oradata
networks:
- backend
healthcheck:
test: [ "CMD", "healthcheck.sh" ]
interval: 10s
timeout: 5s
retries: 10
start_period: 5s
secrets:
- oracle-passwd
- app-passwd

volumes:
oradata-vol:

networks:
backend:

secrets:
oracle-passwd:
external: true
app-passwd:
external: true

Compared to the previous example a network is added to the configuration, and the secret config is slightly different. The image understands that the values are available in /run/secrets and picks them up from there. That’s specific to this image, others might require different parameters.

Furthermore a health check is provided which allows other services to delay their start until the database is up and running. The compose file can easily be extended to start other services, like Oracle REST Data Services. An example can be found on my GitHub, another one is shown next.

Connection Test

The SQLcl container image is a great way of testing connectivity to the database created by compose. Note that it’s necessary to pass the network name and the database container instance name, too. Assuming the compose file lives in a directory named “db”, you can connect as follows:

$ podman run --rm -it \
--name some-sqlcl \
--network db_backend \
container-registry.oracle.com/database/sqlcl:25.1.0 demouser@db_oracle_1/freepdb1


SQLcl: Release 25.1 Production on Wed Jul 02 08:04:10 2025

Copyright (c) 1982, 2025, Oracle. All rights reserved.

Password? (**********?) ******
Connected to:
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.8.0.25.04

SQL> sho user
USER is "DEMOUSER"
SQL>

Easy!

Summary

Podman secrets allow developers to provide information that shouldn’t be part of a container image or (configuration) code to containers. Provided the secrets are set up correctly they provide a much better way of passing sensitive information than hard-coded values on the command line or a Continuous Integration pipeline.

I said they provide a better way, but whilst secrets are definitely a step in the right direction they aren’t perfect (read: secure) out of the box.

Whenever touching the point of sensitive information the advice remains the same: please have someone from the security team review the solution and sign it off before going ahead and using it. There are certainly more secure ways available to provide credentials to applications. Podman secrets however are a good first step in the right direction in my opinion.