Deploy Containers to DigitalOcean Kubernetes with GitHub Actions
This article shows how to deploy a Next.js application to DigitalOcean Kubernetes from GitHub Actions. We'll use the Zero secrets manager with the official Zero GitHub Action to retrieve the DigitalOcean API key as part of the automated deployment process.
Sam Magura
Today we are going to deploy a Next.js application to DigitalOcean Kubernetes from GitHub Actions. We'll use the Zero secrets manager with the official Zero GitHub Action to retrieve the DigitalOcean API key as part of the automated deployment process. This way, the GitHub workflow only needs to be configured with the Zero token — the DigitalOcean API key is not stored in GitHub and does not appear in the workflow's logs.
🔗 The full code for this example is available in the zerosecrets/examples GitHub repository.
Secure your secrets conveniently
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.
Getting Ready for DigitalOcean Kubernetes Development
Kubernetes is a complex tool, but DigitalOcean makes it painless to get a Kubernetes cluster up and running. That said, there are a number of prequisites that must be satisfied before we are ready to deploy containers.
The first step is to sign up at DigitalOcean.com . Note that you'll have to enter a payment method before you can create any resources. Once that is out of the way, click the "Create" button in the upper right and select Kubernetes cluster. Select the smallest cluster size to keep the cost low:
While the cluster is being provisioned, install kubectl (the Kubernetes CLI) and doctl (the DigitalOcean CLI). For doctl
, follow the instructions for enabling integration with kubectl
and Docker.
The next step is to create a DigitalOcean personal access token to enable doctl
to connect to your DigitalOcean account. The token will only be shown once, so we need a secure location to store the token. Zero is perfect for this! If you haven't already signed up for Zero, now is the time to do so. Once you're logged in to the Zero web console, create a new Zero token and click "Add secret". Select DigitalOcean from the dropdown and paste the personal access token in as the value for the TOKEN
secret. Delete the KEY
secret as we will not be using it.
Now, connect doctl
to your DigitalOcean account by running doctl auth init --context <NAME>
where <NAME>
is a string of your choosing. When prompted, paste the DigitalOcean personal access token. Then switch to the newly created context via doctl auth switch --context <NAME>
.
kubectl
needs to be authenticated with the Kubernetes cluster as well. Run
doctl kubernetes cluster kubeconfig save <YOUR_CLUSTER_NAME>
where <YOUR_CLUSTER_NAME>
is the name shown in the DigitalOcean Console — it should look something like k8s-1-23-9-do-0-nyc3-1661101912086
.
Building the Next.js Container
With the initial setup done, we need a container to deploy to our cluster! We'll be containerizing a sample Next.js application, though you could just as easily deploy a container that runs Python, Go, or .NET.
If you don't have Docker installed locally, you can install it here . Then, clone the with-docker
project from the Next.js repository by running
npx create-next-app --example with-docker nextjs-docker --use-npm
Open the nextjs-docker
folder in your favorite editor and inspect the Dockerfile
. Since we're using npm
, let's remove the RUN yarn build
line and uncomment RUN npm run build
. It's a good idea to test that the container works locally before deploying it to the cloud. Build and run the container with:
docker build -t nextjs-docker .
docker run -p 3000:3000 nextjs-docker
Now go to http://localhost:3000/
and you should see a "Welcome to Next.js" webpage!
For Kubernetes to be able to pull our container, we need to push it to a container registry. Create a private DigitalOcean container registry by running:
doctl registry create <YOUR_REGISTRY_NAME>
then log into it with doctl registry login
. Then tag the container image with its fully-qualified name and push it to the registry:
docker tag nextjs-docker registry.digitalocean.com/<YOUR_REGISTRY_NAME>/nextjs-docker:0
docker push registry.digitalocean.com/<YOUR_REGISTRY_NAME>/nextjs-docker:0
Manually Deploying to Kubernetes
We need to do a bit of manual configuration on the Kubernetes cluster before we're ready to automate the deployment with GitHub Actions. Authorize the cluster to access the private container registry by running:
doctl registry kubernetes-manifest | kubectl apply -f -
kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "registry-<YOUR_REGISTRY_NAME>"}]}'
Then tell Kubernetes to run our Next.js container by creating a new deployment:
kubectl create deployment nextjs-docker --image=registry.digitalocean.com/<YOUR_REGISTRY_NAME>/nextjs-docker:0
You can check how many replicas there are of the container with kubectl get rs
:
NAME DESIRED CURRENT READY AGE
nextjs-docker-6bfd4789f6 1 1 1 20s
At this point, the Next.js container is not yet accessible via the web. This can be remedied by creating a load balancer via
kubectl expose deployment nextjs-docker --type=LoadBalancer --port=80 --target-port=3000
The Next.js container listens on port 3000, so this command maps the standard port 80 to port 3000 so that HTTP traffic will be handled by Next. It can take a bit of time for Kubernetes to provision the load balancer. You can monitor its status by running doctl compute load-balancer list --format Name,Created,IP,Status
, which will produce output like
Name Created At IP Status
ac3bc9fe2072e4cd3b2ab6e66240bc2e 2022-08-21T17:48:32Z 159.89.246.192 new
The first time you run this command, the IP may be blank. Continuing running the command periodically until there is an IP address. Now copy the IP address into your browser and you should see the Next.js web app!
If we made some improvements to our Next application, we could redeploy it from the command line like this:
docker build -t registry.digitalocean.com/<YOUR_REGISTRY_NAME>/nextjs-docker:1 .
docker push registry.digitalocean.com/<YOUR_REGISTRY_NAME>/nextjs-docker:1
kubectl set image deployment nextjs-docker nextjs-docker=registry.digitalocean.com/<YOUR_REGISTRY_NAME>/nextjs-docker:1
Remember to increment the number 1
each time you try this.
Automating it with GitHub Actions
Now that we know the commands needed to build, push, and deploy the container image, automating the process with GitHub Actions is relatively straightforward. To get started with GitHub Actions, create a .github
directory in your repository and place a workflows
subdirectory inside of it. Then create the file .github/workflows/main.yml
.
The critical steps of the workflow are fetching the DigitalOcean API key with the Zero GitHub Action and then providing that API key when installing doctl
:
# This fetches the token from your Zero account and stores it in an
# environment variable called ZERO_SECRET_TOKEN
- name: Retrive the DigitalOcean token from Zero
uses: zerosecrets/github-actions/token-to-secrets@main
id: zero
with:
zero-token: ${{ secrets.ZERO_TOKEN }}
apis: ['digital-ocean']
- name: Install doctl
uses: digitalocean/action-doctl@v2
with:
token: ${{ env.ZERO_SECRET_TOKEN }}
The full workflow is available in the zerosecrets/examples repository .
Before pushing the workflow file to GitHub, copy your Zero token from the Zero web console and add it to the GitHub repository as a secret. This can be performed via the settings page:
⚠️ Important: Never commit your Zero token to a git repository! Your Zero token provides access to your DigitalOcean account (and possibly other services) so it must be kept secure.
Now, if you make a tweak to the homepage (index.js
) of the Next application, commit the change and push to GitHub, the workflow will deploy the updated container to Kubernetes. Check that the workflow succeeded and then navigate to the load balancer's IP address in your web browser. If everything worked, you should see the latest version of the Next app.
To clean up, log in to the DigitalOcean web console, select Kubernetes, click the "..." button on your cluster, and select "Destroy cluster".
Conclusion
This guide demonstrated how to set up continuous deployment for a containerized application using DigitalOcean Kubernetes, GitHub Actions, and the Zero secrets manager. The key thing to note is that we never had to add the DigitalOcean personal access token to the GitHub repository. Instead, we added the Zero token to the repository and used the official Zero GitHub Action to fetch the DigitalOcean token when the workflow runs. The design simplifies configuration and makes Zero the single source of truth for all your team's secret keys.
Zero provides the most value for teams who use multiple 3rd party APIs, each with their own API key. In our next post, we'll show how Zero simplifies configuration when building an application that accesses multiple external services.
Additional Resources
Other articles
Securely Handle Secrets in CI/CD with the Zero GitHub Action
Exchange your Zero token for secrets right from your GitHub Actions YAML.
CI/CD Integrations Now Live: GitHub Actions, GitLab, Bitbucket
Zero has just launched new integrations for your favorite CI/CD platforms, which allow you to effortlessly sync your secrets.
Secure your secrets
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.