Zero
Zero
Back

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

Sam Magura

A port with a huge number of shipping containers

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.

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:

The minimal Kubernetes cluster settings in DigitalOcean

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

1
doctl kubernetes cluster kubeconfig save <YOUR_CLUSTER_NAME>
shell

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

1
npx create-next-app --example with-docker nextjs-docker --use-npm
shell

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:

1
2
docker build -t nextjs-docker .
docker run -p 3000:3000 nextjs-docker
shell

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:

1
doctl registry create <YOUR_REGISTRY_NAME>
shell

then log into it with doctl registry login. Then tag the container image with its fully-qualified name and push it to the registry:

1
2
docker tag nextjs-docker registry.digitalocean.com/<YOUR_REGISTRY_NAME>/nextjs-docker:0
docker push registry.digitalocean.com/<YOUR_REGISTRY_NAME>/nextjs-docker:0
shell

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:

1
2
doctl registry kubernetes-manifest | kubectl apply -f -
kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "registry-<YOUR_REGISTRY_NAME>"}]}'
shell

Then tell Kubernetes to run our Next.js container by creating a new deployment:

1
kubectl create deployment nextjs-docker --image=registry.digitalocean.com/<YOUR_REGISTRY_NAME>/nextjs-docker:0
shell

You can check how many replicas there are of the container with kubectl get rs:

1
2
NAME                       DESIRED   CURRENT   READY   AGE
nextjs-docker-6bfd4789f6   1         1         1       20s
shell

At this point, the Next.js container is not yet accessible via the web. This can be remedied by creating a load balancer via

1
kubectl expose deployment nextjs-docker --type=LoadBalancer --port=80 --target-port=3000
shell

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

1
2
Name                                Created At              IP                Status
ac3bc9fe2072e4cd3b2ab6e66240bc2e    2022-08-21T17:48:32Z    159.89.246.192    new
shell

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:

1
2
3
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
shell

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 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 }}
yaml

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:

Adding the Zero token to the GitHub repository as a secret

⚠️ 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