Docker: Setting up Terraform, Ansible, Gitea, Nginx and Keycloak with TLS via Step-Ca
I wanted to learn more about authentication in web apps and the Keycloak project seemed an ideal open source
way for hands-on experimentation. What followed was a docker project to include various components to achieve
this. Also as part of the setup I wanted to add Gitea for usage of IaC code repos for Ansible and Terraform. And
as part of that setup use Nginx streaming options for ssh access via Nginx. I have already decided on a template
approach to my docker projects and various links here of other setups
https://www.rjruss.info/2025/03/setting-up-sap-web-dispatcher-with-tls.html
https://www.rjruss.info/2024/11/docker-setting-up-prometheus-grafana.html
https://www.rjruss.info/2024/11/docker-setting-up-gitea-with-postgres.html
Using my docker template approach I set out for a simple Hello World type project of calling an Ansible
playbook and a Terraform configuration with a very simple Hello World authenticated web app setup. Also to
note, this is for my own home lab and not intended for wider use as step-ca offers its own way to manage
certificate renewal and calling custom scripts - but for the process I developed works for me . The authentication
is where Keycloak is required. Reading further into Ansible and Terraform it would get way more complicated to
actually move beyond Hello World in how to control such IaC platforms via custom web calls. That didn't put me
off :) I set up both to be used independently when connecting the containers themselves. The python code was
“vibe coded” with the help of Gemini, and took a lot of troubleshooting and breaking down to individual steps to
get it to work. E.g. At one point it was luck that I was getting the right key for the tokens and debug steps helped,
but eventually I worked out how to stabilise the code with the help of Gemini. It depends on my next projects if I
want to further vibe code the web calls, but the Keycloak knowledge was the thing I started the whole thing for
and it proved very useful.
As part of the process I eventually added Gitea to the project and set up a one off test script to push a test repo to
the Gitea host via an ssh process. With the ssh keys provided by step-ca.
The following output is the end result of building the docker compose project. (Read on for the pre-reqs before
even getting to this point).
Ansible output extract
..
PLAY [Hello World Playbook] ****************************************************
TASK [Print Hello World] *******************************************************
ok: [localhost] => {
"msg": "Hello, World!"
}
..
Terraform output extract
…
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ hello_world = "Hello, World! from Terraform"
null_resource.hello: Creating...
null_resource.hello: Provisioning with 'local-exec'...
null_resource.hello (local-exec): Executing: ["/bin/sh" "-c" "echo Hello, world from Terraform!"]
null_resource.hello (local-exec): Hello, world from Terraform!
null_resource.hello: Creation complete after 0s [id=4932893277560461013]
…
Gitea output extract
Initialized empty Git repository in /tmp/tmp.gJbulhg6Ms/testrepo/.git/
[main (root-commit) 417d75b] initial setup
1 file changed, 1 insertion(+)
create mode 100644 hellogitearepo.sh
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 233 bytes | 233.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
remote: . Processing 1 references
remote: Processed 1 references in total
To ssh://gitea1.rjruss.org:44022/testuser/test.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
[SUCCESS] pushed repo to gitea
delete local temp directory and repo in gitea manually e.g.
rm -rf /tmp/tmp.gJbulhg6Ms
updated ~/.ssh/config file with following
Host gitea1.rjruss.org
HostName gitea1.rjruss.org
User gitea1
IdentityFile /tmp/tmp.gJbulhg6Ms/testuser_ecdsa
Port 44022
Read previous blogs on more of the background to my use of Docker but to highlight. Again the project
got complicated via some cyber security reasons around storing passwords in clear text and using
TLS all over the place. Some security frameworks state that any clear password is an instant
fail/breach. And personally and from security frameworks TLS is ideal in local networks.
Step 1 setup local user and directory
https://www.rjruss.info/2025/03/setting-up-sap-web-dispatcher-with-tls.html
https://www.rjruss.info/2024/11/docker-setting-up-prometheus-grafana.html
https://www.rjruss.info/2024/11/docker-setting-up-gitea-with-postgres.html
Using my docker template approach I set out for a simple Hello World type project of calling an Ansible
playbook and a Terraform configuration with a very simple Hello World authenticated web app setup. Also to
note, this is for my own home lab and not intended for wider use as step-ca offers its own way to manage
certificate renewal and calling custom scripts - but for the process I developed works for me . The authentication
is where Keycloak is required. Reading further into Ansible and Terraform it would get way more complicated to
actually move beyond Hello World in how to control such IaC platforms via custom web calls. That didn't put me
off :) I set up both to be used independently when connecting the containers themselves. The python code was
“vibe coded” with the help of Gemini, and took a lot of troubleshooting and breaking down to individual steps to
get it to work. E.g. At one point it was luck that I was getting the right key for the tokens and debug steps helped,
but eventually I worked out how to stabilise the code with the help of Gemini. It depends on my next projects if I
want to further vibe code the web calls, but the Keycloak knowledge was the thing I started the whole thing for
and it proved very useful.
As part of the process I eventually added Gitea to the project and set up a one off test script to push a test repo to
the Gitea host via an ssh process. With the ssh keys provided by step-ca.
The following output is the end result of building the docker compose project. (Read on for the pre-reqs before
even getting to this point).
Ansible output extract
..
PLAY [Hello World Playbook] ****************************************************
TASK [Print Hello World] *******************************************************
ok: [localhost] => {
"msg": "Hello, World!"
}
..
Terraform output extract
…
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ hello_world = "Hello, World! from Terraform"
null_resource.hello: Creating...
null_resource.hello: Provisioning with 'local-exec'...
null_resource.hello (local-exec): Executing: ["/bin/sh" "-c" "echo Hello, world from Terraform!"]
null_resource.hello (local-exec): Hello, world from Terraform!
null_resource.hello: Creation complete after 0s [id=4932893277560461013]
…
Gitea output extract
Initialized empty Git repository in /tmp/tmp.gJbulhg6Ms/testrepo/.git/
[main (root-commit) 417d75b] initial setup
1 file changed, 1 insertion(+)
create mode 100644 hellogitearepo.sh
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 233 bytes | 233.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
remote: . Processing 1 references
remote: Processed 1 references in total
To ssh://gitea1.rjruss.org:44022/testuser/test.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
[SUCCESS] pushed repo to gitea
delete local temp directory and repo in gitea manually e.g.
rm -rf /tmp/tmp.gJbulhg6Ms
updated ~/.ssh/config file with following
Host gitea1.rjruss.org
HostName gitea1.rjruss.org
User gitea1
IdentityFile /tmp/tmp.gJbulhg6Ms/testuser_ecdsa
Port 44022
Read previous blogs on more of the background to my use of Docker but to highlight. Again the project
got complicated via some cyber security reasons around storing passwords in clear text and using
TLS all over the place. Some security frameworks state that any clear password is an instant
fail/breach. And personally and from security frameworks TLS is ideal in local networks.
Step 1 setup local user and directory
Creating a dedicated docker user in this example dockeruser1
- if this is changed the .env file needs to be adapted in step 2
The directory where the downloaded project is stored can be adapted as well.
The example uses /srv/docker-config
Example using the name of the downloaded zip as ansible-terraform-nginx-keycloak-gitea-postgres-stepca-docker.zip
Creating a dedicated docker user in this example dockeruser1
- if this is changed the .env file needs to be adapted in step 2
The directory where the downloaded project is stored can be adapted as well.
The example uses /srv/docker-config
Example using the name of the downloaded zip as ansible-terraform-nginx-keycloak-gitea-postgres-stepca-docker.zip
Step 2 Adapt the .env file
Change bold entries if required and ignore any other settings in the .env file
Local docker user and group needs to match the user created in step 1
The above would be allow the services to be accessed
Via the docker user
Adapt the dockeruser1 - group dockeruser1 is used by default
Via the shared group
Adapt the group id of 5501 a new group will be created with that gid
Via a new directory/volume (it will be created if it does not exist)
Adapt the /docker1
Via the following domain, hostname and ports
rjruss.org = this needs to be adapted to the required domain
hawalma1- = is the base pre-fix of all local hosts that are not part of the proxy
keycloak1.${DOMAIN} = resolved via nginx settings to point to keycloak
proxy1.${DOMAIN} = resolved via nginx settings to point to test httpbin site (for testing)
ansible1.${DOMAIN} = resolved via nginx settings to point to Ansible
terraform1.${DOMAIN} = resolved via nginx settings to point to Terraform
gitea1.${DOMAIN} = resolved via nginx settings to point to Gitea
Adapt Step CA
VM_APP_STEP_PORT=9006 # Port used by Step CA
These parameters control the name of the STEP services (root certificate name)
VM_PROV_NAME=baseprov
VM_AUTH_NAME=basestepCA
Adapt certificate renewal process sets the certificate renewal time, useful to set the VM_RDAY as tomorrow and VM_RTIME at an appropriate renewal time
(postgres is not open to connections other than on the docker network and uses the standard postgres port 5432
inside the docker network)
Step 3 adapt the age related password files
Change bold entries if required and ignore any other settings in the .env file
Local docker user and group needs to match the user created in step 1
The above would be allow the services to be accessed
Via the docker user
Adapt the dockeruser1 - group dockeruser1 is used by default
Via the shared group
Adapt the group id of 5501 a new group will be created with that gid
Via a new directory/volume (it will be created if it does not exist)
Adapt the /docker1
Via the following domain, hostname and ports
rjruss.org = this needs to be adapted to the required domain
hawalma1- = is the base pre-fix of all local hosts that are not part of the proxy
keycloak1.${DOMAIN} = resolved via nginx settings to point to keycloak
proxy1.${DOMAIN} = resolved via nginx settings to point to test httpbin site (for testing)
ansible1.${DOMAIN} = resolved via nginx settings to point to Ansible
terraform1.${DOMAIN} = resolved via nginx settings to point to Terraform
gitea1.${DOMAIN} = resolved via nginx settings to point to Gitea
Adapt Step CA
VM_APP_STEP_PORT=9006 # Port used by Step CA
These parameters control the name of the STEP services (root certificate name)
VM_PROV_NAME=baseprov
VM_AUTH_NAME=basestepCA
Adapt certificate renewal process sets the certificate renewal time, useful to set the VM_RDAY as tomorrow and VM_RTIME at an appropriate renewal time
(postgres is not open to connections other than on the docker network and uses the standard postgres port 5432
inside the docker network)
The age (see step 4) command is used to store passwords in an encrypted file on a docker volume. These can be
shared between containers via the bash scripts in the base docker container build.
These .*.info files should be deleted ***after completing the step 4 process***
#Postgres DB connection is certificate and password based (password can be used if postgres config adapted with
the required password setup)
The age (see step 4) command is used to store passwords in an encrypted file on a docker volume. These can be
shared between containers via the bash scripts in the base docker container build.
These .*.info files should be deleted ***after completing the step 4 process***
#Postgres DB connection is certificate and password based (password can be used if postgres config adapted with
the required password setup)
Step 4 Create Volumes, Networks and encrypted passwords
Following script installs operating system pre-req packages, volumes, networks and encrypts the passwords.
After running the script the following volumes and network will be available.
docker volume ls |grep vm
local vm_iac_ansible_vol1
local vm_iac_gitea_vol1
local vm_iac_info_vol1
local vm_iac_keycloak_vol1
local vm_iac_keys_vol1
local vm_iac_nginx_vol1
local vm_iac_postgitea_vol1
local vm_iac_postgres_vol1
local vm_iac_step_vol1
local vm_iac_terraform_vol1
docker network ls |grep vm
d95e508e4dc7 vm-iac-net1 bridge local
**** delete these password files ****ENSURE YOU KNOW THE PASSWORDS before deleting****
ls -a .*.info
Following script installs operating system pre-req packages, volumes, networks and encrypts the passwords.
After running the script the following volumes and network will be available.
docker volume ls |grep vm
local vm_iac_ansible_vol1
local vm_iac_gitea_vol1
local vm_iac_info_vol1
local vm_iac_keycloak_vol1
local vm_iac_keys_vol1
local vm_iac_nginx_vol1
local vm_iac_postgitea_vol1
local vm_iac_postgres_vol1
local vm_iac_step_vol1
local vm_iac_terraform_vol1
docker network ls |grep vm
d95e508e4dc7 vm-iac-net1 bridge local
**** delete these password files ****ENSURE YOU KNOW THE PASSWORDS before deleting****
ls -a .*.info
Step 5 test hosts, build and run it
Network DNS resolution checks
Before building the docker compose project it is essential to check that the proxy hosts and dedicated hosts
resolve as required.
**SUCCESSFUL checks
The above example is from a test server to confirm this works on another server :)
The following hosts need to resolve to make this setup work
Proxy related hosts (resolved via nginx connection)
ansible1.rjruss.org resolved
keycloak1.rjruss.org resolved
proxy1.rjruss.org resolved
terraform1.rjruss.org resolved
gitea1.rjruss.org resolved
Direct related hosts that need to succeed on the resolution
hawalma1-gitea.rjruss.org resolved
hawalma1-keycloak1.rjruss.org resolved
hawalma1-step.rjruss.org resolved
***WARNING checks
The warnings are for future work or maybe not used. The hosts are part of the template based approach and for
direct connections to the containers
Could take a while depending on the server/computer performance! as it builds quite a but
Wait until you see the keycloak container logs indicate that the client-scope has completed and the health check is
UP
Before building the docker compose project it is essential to check that the proxy hosts and dedicated hosts
resolve as required.
**SUCCESSFUL checks
The above example is from a test server to confirm this works on another server :)
The following hosts need to resolve to make this setup work
Proxy related hosts (resolved via nginx connection)
ansible1.rjruss.org resolved
keycloak1.rjruss.org resolved
proxy1.rjruss.org resolved
terraform1.rjruss.org resolved
gitea1.rjruss.org resolved
Direct related hosts that need to succeed on the resolution
hawalma1-gitea.rjruss.org resolved
hawalma1-keycloak1.rjruss.org resolved
hawalma1-step.rjruss.org resolved
***WARNING checks
The warnings are for future work or maybe not used. The hosts are part of the template based approach and for
direct connections to the containers
Could take a while depending on the server/computer performance! as it builds quite a but
Wait until you see the keycloak container logs indicate that the client-scope has completed and the health check is
UP
Step 6 Run the test Ansible and Terraform scripts
Run “test-ansible.sh” and “test-terraform.sh” to confirm the setup is working as expected. The scripts download
the required Step based CA root certificate locally to the docker host and then calls the containers via the
nginx proxy. Take a look at the scripts for the setup.
Start a new terminal session
su - dockeruser1
cd /srv/docker-config/ansible-terraform-nginx-keycloak-gitea-postgres-stepca-docker/
#Run the Ansible test script
The script calls a python web app running on the ansible container via the nginx proxy to get the token from
Keycloak. The using the token calls Ansible again to run the playbook
A successful call is shown above with the “Hello, World!” output
#Run the Terraform test script
Using the same principal as the Ansible setup but calling the Terraform container to run the configuration.
A successful call is shown above with the Hello, World! from Terraform output
#Run Gitea test script
This test for Gitea is a one off run to test its all working. A few changes would need to be made to the script if there
are any errors. It downloads the ssh certificate for Gitea then pushes a hellogitearepo.sh script to Gitea. It works if [SUCCESS] is output as per below
Certificate Renewal Control Script(s)
The above scripts will set the certificate renewal to the following settings
#The date and time the certificate check will take place
Wake up time to check certificate : 30-08-2025 18:30:00
#The controlling scripts will sleep every 2 minutes before checking date and time for
# certificate renewal
Sleep interval : 2m
#Not used in this project. This is an option to check that the certificate has X days left
Days remaining for certificate expiry check : 1
#This is where Step CA command will check that the certificate still valid
Percentage for certificate expiry check : 80%
#Length of the renewed certificate -
Length in days a new certificate is valid for : 1
Appendix
A: General Information: Keycloak
To access the keycloak via the web admin pages you need the Step root certificate on your host computer. A
helper script as follows is setup to cut and paste the command to get this on a windows computer via powershell.
Run,
./display_web_urls.sh
## automateit-step-run
## STEP powershell to trust certificate - added as a trusted root so use it on that understanding
downloading step CA certificate to /var/tmp/stepCA.pem
Successfully copied 2.56kB to /var/tmp/stepCA.pem
I have enclosed the powershell in the table to run on a windows computer via powershell prompt
Install step-ca root certificate on host windows
Answer YES at the prompt
I have a homelab Hyper-V landscape with a windows DNS host, and it is important to set up dns resolution for all
the proxy hosts in this project
For my local windows based P.C I updated the host file with the proxy hosts from earlier.
My example I need the Keycloak proxy URL and Nginx Proxy port e.g.
https://keycloak1.rjruss.org:44340/
The new admin user is hard coded as newuser the password is the PW defined in the .KEYCLOAK.info file from
earlier (that should have been deleted after noting down what the password was ;))
Keycloak Realms
I am no Keycloak expert and the realm approach is open to interpretation on how to set these up for any use case.
So my setup is a home lab with few users, therefore I decided on a dedicated realm for each use case - it is
obviously best to check out realm usage for any production based projects.
I used a naming convention to identify which Keycloak service/function was for each by noting -an- for Ansible
and -tr- for Terraform. E.g. auto-an-realm is for the public based client defined in Keycloak for the Ansible. All
the configuration is set up automatically via the kcadm.sh cli within the startup scripts for the Keycloak container.
There is no manual intervention required to run the test Hello World web apps. Check out the scripts for these
kcadm.sh commands and flow.
Screenshot shows the defined realms that are all created when starting the project for the first time. (There is a
private client realm and client defined but not currently used).
To access the keycloak via the web admin pages you need the Step root certificate on your host computer. A
helper script as follows is setup to cut and paste the command to get this on a windows computer via powershell.
Run,
./display_web_urls.sh
## automateit-step-run
## STEP powershell to trust certificate - added as a trusted root so use it on that understanding
downloading step CA certificate to /var/tmp/stepCA.pem
Successfully copied 2.56kB to /var/tmp/stepCA.pem
I have enclosed the powershell in the table to run on a windows computer via powershell prompt
Install step-ca root certificate on host windows
I have a homelab Hyper-V landscape with a windows DNS host, and it is important to set up dns resolution for all
the proxy hosts in this project
For my local windows based P.C I updated the host file with the proxy hosts from earlier.
My example I need the Keycloak proxy URL and Nginx Proxy port e.g.
https://keycloak1.rjruss.org:44340/
The new admin user is hard coded as newuser the password is the PW defined in the .KEYCLOAK.info file from
earlier (that should have been deleted after noting down what the password was ;))
Keycloak Realms
I am no Keycloak expert and the realm approach is open to interpretation on how to set these up for any use case.
So my setup is a home lab with few users, therefore I decided on a dedicated realm for each use case - it is
obviously best to check out realm usage for any production based projects.
I used a naming convention to identify which Keycloak service/function was for each by noting -an- for Ansible
and -tr- for Terraform. E.g. auto-an-realm is for the public based client defined in Keycloak for the Ansible. All
the configuration is set up automatically via the kcadm.sh cli within the startup scripts for the Keycloak container.
There is no manual intervention required to run the test Hello World web apps. Check out the scripts for these
kcadm.sh commands and flow.
Screenshot shows the defined realms that are all created when starting the project for the first time. (There is a
private client realm and client defined but not currently used).
B: General Information Gitea
As part of the docker build a testuser is created for Gitea and an SSH key information is automatically uploaded
for use. SSH keys can be found at the below location once logged on as the testuser.
As part of the docker build a testuser is created for Gitea and an SSH key information is automatically uploaded
for use. SSH keys can be found at the below location once logged on as the testuser.
Miscellaneous actions
Remove demo certs from certstore, from the example code only - if the root CA name is changed then
the *basestep* match won't match any…..
Remove demo certs from certstore, from the example code only - if the root CA name is changed then
the *basestep* match won't match any…..
No comments:
Post a Comment