Deploy a container to a server
If you’re deploying a containerized application now days there is a chance you’re using Docker Compose.
I love Docker Compose and it is great! But there is a number of smaller issues that make it quirky to work with for any kind of production use case.
The problems
With this setup you’re likely to have a server with a docker-compose.yml file stored on it and you’re deploying your application by SSHing into your server & running docker-compose up -d.
For the most basic setups this is fine, but there is a few key flaws with this approach:
- Remote connection: If you want to deploy the application, then you will need to SSH into the server. A simple task for many but it’s not always convenient or practical.
- Temporary downtime: Once you run
docker compose up -dit will tear down your current containers in order to spin up the new ones. This can cause downtime for your application. - Failure rollbacks: Following from the above, if when your new containers are spun up they fail to start, Docker does not automatically rollback to the previous containers to keep your application running.
- Updating the config: In order to make any changes to the service configuration, whether that be adding another application or changing a current one, like updating a container image version, you need to copy the file from your local machine to the server.
The solution(s)
Well, what if I told you that there is a way to solve all of these issues, with 2 simple tools?
Let me proudly introduce you to Docker Context and Docker Stack.
With these tools you can both deploy your containers to a remote server without ever needing to log in yourself, and also have zero downtime when deploying your application(s) thanks to rolling updates.
What is Docker Context?
By default when you run a Docker command, it will run against the local Docker daemon by sending requests to your local Docker socket.
With Docker Contexts, you can change this so instead of running Docker commands against your local Docker daemon, you can run them against a remote Docker daemon.
To make this even better, Docker lets you store multiple of these contexts so you can easily switch between them. So let’s say you have lots of servers. You can create a context for each server and then easily switch between them.
Creating a context
To get started using Docker Contexts, you first need to create a new context to point to your server.
docker context create my_server --docker "host=ssh://user@server_ip_address"Let’s break down the key parts here that you may want to change:
my_server- This is the name of the actual Docker context named locally.user- This is the name of the user you use to connect to your server with via SSH.server_ip_address- This is the domain name or IP address of the server you want to connect to.
Using the context
Now that you have created your first context pointing to your server, you can now switch to using it in order to run Docker commands against your server.
docker context use my_serverThis will set your “current selected context” to the one you just created.
And now to test it, you can run a couple of basic commands to see if it’s working.
docker infodocker psdocker imagesAnd just like that you can now run Docker commands against your server without ever needing to SSH into it yourself.
But say you want to switch back to running commands against your local Docker daemon, you can do so by running:
docker context use defaultWhat is Docker Stack?
Next up is Docker Stack, which is basically Docker Compose on steroids. It allows you to deploy a stack of services or applications defined in a YAML file, just like Docker Compose.
In short, you build a “stack” like you would with Docker Compose that is made up of multiple services.
The key difference though is a few different parts of services you can configure & some different commands when you deploy or make changes to your stack.
With this it will mean that your application stays online the whole time, that the new containers are running & healthy before removing the old ones and lastly even offer you the option to rollback to the older containers if you realise later on that something is broken.
Configuring Docker Stack
Now, in order to get started using Docker Stack on your server the is one key thing you need to do first.
Under the hood, Docker Stack uses Docker Swarm to manage the services and containers. Now usually Docker Swarm is used for managing multiple servers, but it’s perfectly fine & valid to use it on a single server.
To enable Docker Swarm on your server you can run the following command:
docker swarm initOn some cloud providers, you may need to pass in the --advertise-addr flag to specify the IP address of the server if Docker cannot detect it automatically.
docker swarm init --advertise-addr=server_ip_addressOnce successful, this command should print out a token that you can use to join other servers to the swarm. But as we’re only using a single server, you don’t need to worry about this. And should you ever need this token again in future you can run docker swarm join-token [manager|worker] to get it.
Deploying a stack
After you’ve set initialized Docker Swarm on your server, you can now deploy a stack to it.
To get started we’ll deploy a basic stack that spins up an Nginx container listening on port 80.
services:
web:
image: nginx:latest
ports:
- 80:80
deploy:
update_config:
order: start-firstNow, to most people this looks pretty much like a regular Docker Compose file. However, there is that one extra bit at the bottom, deploy, which is where you can configure how Docker Stack should deploy your services.
In this case, we’re telling Docker Stack to create new containers before removing the old ones. This enables rolling releases & ensures that there is no downtime when deploying changes.
With the file above created locally, you can then run the following command to deploy the stack to your server.
Make sure you the -c path points to the location of your docker-compose.yml file you created above.
docker stack deploy -c /path/to/docker-compose.yml my_stackThe last argument here, my_stack, will be the name of the stack you’re deploying. This can be anything you want it to be.
Once completed, you should be able to visit the IP address of your server in your browser, http://server_ip_address, and see the standard Nginx welcome page.
Tada! You’ve just deployed a container to a server without having to connect to it, copy some files or anything complicated.
Making changes
With it all deployed and working, now what? What happens if you want to make a change?
Well, just run that same command.
docker stack deploy -c /path/to/docker-compose.yml my_stackRunning this trigger a new deployment to start. Meaning that Docker will attempt to deploy changes, if any, and when that happens it will begin to roll out a new release. This means that it will spin up new containers with the new configuration, and only once they are healthy will it remove the old containers.
Shit! Go back!
Something is bound to go wrong at some point. Miss one little detail. Configure somethong wrong or whatever it may be.
You now need to quickly revert your production deployment back to the last revision.
Well, to do so is quite simple. Simply run the following command:
docker service rollback my_stackThis will roll back a specified service to its previous version from the swarm.
The extra stuff
Now that it’s all up and running, here is a few handy commands you’ll likely want to manage your stack:
docker stack lsdocker stack services my_stackdocker service lsdocker service logs my_stack_webdocker service update --force my_stack_webConclusion
Docker Compose is great for deploy a basic stack of services or applications. But if you want to step it up a notch and deploy changes with zero downtime & remotely without needing to log in to a server, then Docker Context & Docker Stack are the way to go!
Oh, and don’t forget to switch your context back to your local Docker daemon when you’re done 😉.
docker context use default