Skip to content

Instantly share code, notes, and snippets.

@jonfriesen
Created September 16, 2021 18:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonfriesen/1ce7899a1ecb7c7b9032b2bcc3b27315 to your computer and use it in GitHub Desktop.
Save jonfriesen/1ce7899a1ecb7c7b9032b2bcc3b27315 to your computer and use it in GitHub Desktop.

Quick Start with Go

Overview

In this developer guide we are going to cover the considerations, strategies, and pitfalls to optimally host your Go app on App Platform. If you come across something that isn't covered in this guide ask a community question.

Before you start

We make the assumption that you've already have a Go app. To get start with Go checkout the official Getting Started with Go documentation.

To deploy a Go app on App Platform, you will need a couple things:

  1. A DigitalOcean account (signup)
  2. A Go app located in
    1. Either GitHub or GitLab
    2. A public DockerHub repository
    3. A DigitalOcean Container Registry

If you're just learning App Platform, we have a sample Go app you can fork on GitHub.

Creating an app

Initial Creation

Creating an for the first time is easily done on the Apps tab in DigitalOcean cloud dashboard. Before starting the creation, it's good to consider what things you will need to located, build, and configure your app at both build and run time.

Some things to consider before creation:

  • If your app is not in the root of your Git repository, what is the path?
  • What configurations need to be done to startup successfully?
  • Does your app expect any external dependencies? (such as a database or third party API)
  • Can the configurations you need be added via environment variables?
  • Does your app need any special build arguments?
  • Does your app need any special run arguments?
  • What port does your app's http server listen on?
    • Is it configurable via the $PORT environment variable?

App Platform supports two methods of build apps, Dockerfiles and Cloud Native Buildpacks. Buildpacks are the lowest barrier to entry and support most apps right out of the gate. More details on the buildpack, customization, and limitations can be found here.

Cloud Native Buildpacks

When creating an app, App Platform scans the app source code in the App Platform detection service which will determine which language, framework, or if Dockerfiles are to be used for the build. App Platform will default to Dockerfile builds if a dockerfile is found in the root of the project directory, otherwise it will look for any of the following files to detect a Go application:

  • go.mod
  • Gopkg.toml
  • Godeps/Godeps.json
  • vendor/vendor.json
  • glide.yaml
Private dependencies

If your Go app uses private dependencies that are not accessible with the same account used to access the apps a personal access token can be injected by setting an environment variable with the key of GO_GIT_CRED__HTTPS__GITHUB__COM and the value of a GitHub personal access token. Note, this has only been tested with GitHub. More details can be found here.

Dockerfile

Using a Dockerfile to build a Go app is a flexible way to optimize the build image that will be used to deploy the app. By placing a Dockerfile in the root of the directory or specifying the path to a dockerfile in the app spec App Platform will detect the Go app as a dockerfile based app. One of the advantages of a Dockerfile based build is the image can be cut down really small with multi-stage builds. For example, digitalocean/sample-dockerfile uses multi-stage build so the final container image only contains updated CA certificates and the compiled Go binary:

# This is a standard Dockerfile for building a Go app.
# It is a multi-stage build: the first stage compiles the Go source into a binary, and
#   the second stage copies only the binary into an alpine base.

# -- Stage 1 -- #
# Compile the app.
FROM golang:1.12-alpine as builder
WORKDIR /app
# The build context is set to the directory where the repo is cloned.
# This will copy all files in the repo to /app inside the container.
# If your app requires the build context to be set to a subdirectory inside the repo, you
#   can use the source_dir app spec option, see: https://www.digitalocean.com/docs/app-platform/references/app-specification-reference/
COPY . .
RUN go build -mod=vendor -o bin/hello

# -- Stage 2 -- #
# Create the final environment with the compiled binary.
FROM alpine
# Install any required dependencies.
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Copy the binary from the builder stage and set it as the default command.
COPY --from=builder /app/bin/hello /usr/local/bin/
CMD ["hello"]

Configurations

The process for creating an involves 4 steps, selecting the code base, configuring the detected service, selecting the name and region, and selecting the instance type and size. More configurations, services, static sites, and databases can be added after the initial creation if needed.

1. Selecting the code base

App Platform has native integration with GitHub and GitLab. In Addition, pre-built images hosted in Docker Hub (must be public) or DigitalOcean Container Registry are supported as well. See our How to Deploy from a Container Image tutorial for more information.

After authenticating with GitHub or GitLab, repositories can be selected alongside the branch. You will also be able to indicate if changes to this branch should be auto-deployed.

2. Configuring the detected service

During this step App Platform will scan the code base and detect the language and framework. The source is not located in the root directory, the option to specify a sub-directory will be provided after the first scan attempt. Once the Go app is detected, configuration can start.

During this part of app creation, environment variables, build and run commands, the default HTTP port, and subroutes can be specified. More in-depth configurations can be done after creation by modify in the UI or by editing the app spec.

Environment Variables are included at both run and build time by default. This can be customized within the app spec later if needed. We recommend using the Encrypt option for secrets that should not be visible after creation. This feature encrypts the value into a secret box which is included in the app spec, the secret is bound to this app and is only decrypted before injection to the app.

Build and run commands are automatically detect, if you have special arguments they can be added at this point. In some cases, users will run startup scripts before starting their Go app to get the environment ready for show time.

// TODO add example of using Go LDFLAGS (is this possible ๐Ÿ˜ฑ??)

HTTP Port is the port that an app is expecting traffic on. If your app uses the PORT environment variable you can leave this as is, otherwise it can be changed to match the port that it set in your app. App Platform will automatically accept traffic on port 80 and 443 and forward it to this port. Note, if you set a PORT environment variable, that value will be overwritten by this one.

HTTP Request Route is the subpath that this component will be accessible as. If this is the only component in the app then the request route will be /. In apps with multiple public facing components routes direct to those, for example, an app composed of a static site frontend and a Go API may have the static site on request route / and the Go API availabe at /api.

During this step a database can be attached or created. App Platform offers a low cost Postgres development database as well as the ability to attach production databases that were created in the Databases dashboard. Attaching a database as a component allows configurations to be injected at run time, for example an environment variable that will inject the connection string to a database component called "db": DATABASE_URL=${db.DATABASE_URL}. These are called bind variables and have a whole host of applications.

3. Selecting a name and region

App names are used within a provided domain with a random five character string appended, most apps will eventually use a custom domain like example.com instead of the provided domain though it's good for testing.

When it comes to region it will be most beneficial to pick a region that is close to your users to minimize latency.

4. Instance type and size

Go apps cannot be static sites, so we will be looking at Basic and Pro tier apps. Basics are the most economical and usually the right choice for apps that are simple or early in their development. Apps can be effortlessly upgraded to Pro tier with zero downtime if needed.

Most apps can be deployed on either Basic or Pro tiers, some of the advantages of a Pro tier app is more bandwidth and build minutes, access to dedicated CPU instances, per-minute metrics, and horizontal scaling.

Apps can be scaled in two ways, vertically means that the instance the app is running on has it's resources. An example of horizontal scaling is increasing the instance size from 1 vCPU and 512MB to 2 vCPU and 4GB RAM. This will help that app handle more load and produce a faster experience but will not help with the reliability of the app in the event of a crash. Horizontal scaling, offered in the Pro tier, is when the app can be scaled to multiple instances, instead of a single instance with lots of resources, the App is hosted in multiple small instances, if one crashes the others will handle traffic while App Platform recovers the crashed instance.

๐Ÿš€ Houston we have lift off! ๐Ÿš€

After clicking the Launch button the configured app will be build, deployed, and provided a domain. The build and deploy details can be found in the Deployments tab

Common Pitfalls

http serving on localhost vs 0.0.0.0

A common issue we see is HTTP servers explicitly running on localhost or 127.0.0.1. This is problematic because localhost is an alias for 127.0.0.1 which is a local loopback interface which is not exposed external to the current environment. Go HTTP servers should not include an address (defaulting to 0.0.0.0) or specify 0.0.0.0 explicitly, for example:

func main() {
    http.HandleFunc("/", HelloEndpoint)
    // Don't use these:
    // http.ListenAndServe("localhost:8080", nil)
    // http.ListenAndServe("127.0.0.1:8080", nil)
    // 
    // Use these:
    // http.ListenAndServe("0.0.0.0:8080", nil)
    // This is the best practice method:
    http.ListenAndServe(":8080", nil)
    
}

func HelloEndpoint(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Well hello, %s!", r.URL.Path[1:])
}

HTTP Port configuration

Some apps set the listening port using the PORT environment variable, while others are hardcoded. In the scenario that an app uses PORT to determine the http server port, the HTTP Port field will overwrite any PORT environment variables that are set in the app.

If an app's HTTP port is hardcoded the HTTP Port for that component needs to be changed to match. We use this value to direct traffic to the app.

App Operations

Security

App Platform offers a number of features that can be used to secure your app and the underlying.

  • Encrypted Environment Variables, environment variables can be specified individual for both build and run time, as well as app wide and component. These variables can be encrypted as well which binds them to the app and makes them immutable. They are injected JIT for both build and run time events.
  • Database connections can be made using trusted sources integration which registers there App Platform app with the managed database and will only allow connections from the specified app.
  • Automatic HTTPS (TLS) will ensure traffic is encrypted over the wire. All HTTP traffic is redirected to HTTPS.

Insights

The Insights tab in the DigitalOcean Apps dashboard shows graphs that follow all of an apps components. Alongside seeing CPU and Memory usage, insights offer restart counts, bandwidth, and latency metrics. More details.

Alerts and Notifications

App Platform offers basic alerting and notification via email or with Slack integration that tracking resource usage as well as successful and failed deployments. More details.

Scaling

Scaling is describe in Instance type and size, see our scaling dedicated documentation.

Jobs & Workers & Internal Services! Oh my!

There are several component types can be useful for certain cases. Here's brief overview of each with links for more in-depth details.

  • Workers are meant for doing work without being accessed external. This means that a worker component will have to reach out to an external source to get instructions on what it needs to do. If an app worker component wants instructions passed in, a internal service would probably be a better fit.
  • Internal Services are similar to services but expose ports internally. A service can expose external and internal ports.
  • Jobs are one off runs that happen around deployments, this can be on before a deployment, after a deployment, or when a deployment fails. Jobs are great for starting database migrations or executing code on a failed deployment.

Logs

Run time logs are accessible from the Runtime Logs tab, this tab will have all of the live logs for the app components that support logging. These logs will include standard out and standard error. More details on logs.

In addition, build and deployment logs can be found for each deployment under the Deployments tab. Here you can find the build and deployment logs for each of your components for a specific deployment. More details on deployments.

// TODO external logging?

Troubleshooting

Builds

Failed builds can be a frustrating, luckily App Platform preserves logs and posts them under the deployment. You'll be able to see both the App Platform logs and your build logs as they happen.

Deployments

Deployment logs occur after a build has been successful. At this point, the container image of the app created by App Platform is being pulled from a private container registry and deployed to an App Platform cluster.

Live Apps

Debugging a live app offers a few more tools than the simple log offering of builds and deployments. Not only are the live logs available under the Runtime Logs tab, console access to component instances is available as well.

Continuous Integration/Continuous Deployment (CI/CD)

App Platform works well with CI/CD settings in a multiple of different scenarios. Turning off auto-deployment and triggering a build after tests are done, or on a new version release, or adhoc, it's really up to you. These can be done via our doctl cli tool. Some common paths are:

  • Triggering a deploy on version release
  • Running tests before deployment
  • Build a Docker image and pushing that to DigitalOcean Container Registry or Docker Hub, then setting that image to be used for deployment
  • Waiting for multiple repositories (for multiple components) to be done before deployments are triggered

All of these scenarios and more are possible.

GitHub Actions

App Platform has a GitHub Action called app_action that can be used to easily trigger deployments on a variety of changes from new commits on a branch, releases, tags, merges, you name it. It also supports dynamically updating app specs to pull specific docker image versions.

DOCTL (CLI)

App Platform operations can be perform from almost any CI/CD environment using the DOCTL CLI tool. For more details please see Reference for the DigitalOcean Command-Line Interface (doctl) with App Platform.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment