There is a great cross-platform build automation tool - Cake.Net.

I started using it and created some useful scripts to Release and Publish new version of application.

I am using AngularJS as frontend framework, asp net core (dotnet core) as backend framework, git as source system and docker to deploy and run application on remote server.

So it is a small cake script to release a new version of application:

#addin "Cake.Docker"
#addin "Cake.FileHelpers"

// Release: ./build.sh -t Release "-packageVersion=x.x.x.x"
// Publish: ./build.sh -t Publish "-packageVersion=x.x.x.x" "-env=one"
// Delete release: ./build.sh -t Delete "-packageVersion=x.x.x.x"
var target = Argument<string>("target");
var version = Argument<string>("packageVersion");

public class Settings
{
    public string SshAddress;
    public string SshPort;
    public string HomePath;
}

Settings sets;

Task("SelectEnvironment")
    .Does(()=>{
        var env = Argument<string>("env");
        var environments = new Dictionary<string, Settings> {
            ["one"] = new Settings {
                SshAddress = "root@66.66.66.66",
                SshPort = "22",
                HomePath = "/root/Templates"
            },
            ["two"] = new Settings{
                SshAddress = "root@99.99.99.99",
                SshPort = "26",
                HomePath = "/home/Templates"
            }
        };
        sets = environments[env];
});

Task("Publish")
    .IsDependentOn("SelectEnvironment")
    .IsDependentOn("CheckAllCommitted")
    .IsDependentOn("CheckoutTag")
    .IsDependentOn("BuildAngular")
    .IsDependentOn("BuildDocker")
    .IsDependentOn("CheckoutBranch")
    .IsDependentOn("PushToGitLab")
    .IsDependentOn("PublishService")
    .Does(()=>
    {
        Information("Finished!");
    });

Task("Release")
    .IsDependentOn("SetVersion")
    .IsDependentOn("ReleaseNotes")
    .IsDependentOn("Commit")
    .IsDependentOn("Build")
    .IsDependentOn("PushTagToGit")
    .Does(()=>
    {
        Information("Finished!");
    });

//build angular
Task("Build Angular")
    .Does(() =>
    {
        StartProcess("ng", "build");
    });

// delete release (tag)
Task("Delete")
    .Does(()=>
    {
        StartProcess("git", $"tag --delete v{version}");
        StartProcess("git", $"push --delete origin v{version}");
    });

// add release notes to historys
Task("ReleaseNotes")
    .Does(()=> {
        IEnumerable<string> redirectedOutput;
        StartProcess("git", 
            new ProcessSettings {
                Arguments = $"describe --abbrev=0 --tags",
                RedirectStandardOutput = true
            },
            out redirectedOutput
        );
        var previousVersion = redirectedOutput.First();
        Information(previousVersion);
        IEnumerable<string> redirectedOutput2;
        StartProcess("git", 
            new ProcessSettings {
                Arguments = $"log --pretty=\"%s\" HEAD...{previousVersion}",
                RedirectStandardOutput = true
            },
            out redirectedOutput2
        );
        FileAppendLines("./src/HISTORY.txt", new string[]{$"------release v{version}-----"});
        FileAppendLines("./src/HISTORY.txt", redirectedOutput2.ToArray());
    });

// push docker image to gitlab
Task("PushToGitLab")
    .Does(()=>{
        StartProcess("docker", $"push registry.gitlab.com/counterra/service:{version}");
    });

// build project locally 
Task("Build")
    .Does(() =>
{
    DotNetCoreRestore("./src/counterra.csproj");
    CleanDirectory("./artifacts");
    var settings = new DotNetCorePublishSettings
    {
        Framework = "netcoreapp1.1",
        Configuration = "Release",
        OutputDirectory = "./artifacts/"
    };
    DotNetCorePublish("./src/counterra.csproj", settings);
});

// set new version in project file
Task("SetVersion")
    .Does(()=>
    {
        var file = File("./src/counterra.csproj");
        XmlPoke(file, "/Project/PropertyGroup/Version", version);
    });

// build docker
Task("BuildDocker")
    .Does(() =>
{
    var settings = new DockerBuildSettings {
        Tag = new []{ $"registry.gitlab.com/counterra/service:{version}" }
    };
    DockerBuild(settings, ".");
});

// go to remote server and replace docker there
Task("PublishService")
    .Does(() =>
{
    StartProcess("scp", $"-P {sets.SshPort} {sets.SshAddress}:{sets.HomePath}/server/docker-compose.yml ./artifacts/");
    ReplaceRegexInFiles("./artifacts/docker-compose.yml", @"registry.gitlab.com/counterra/service:\d+\.\d+\.\d+\.\d+", $"registry.gitlab.com/counterra/service:{version}");

    StartProcess("ssh", $"-p {sets.SshPort} {sets.SshAddress} cd {sets.HomePath}/server/; docker-compose stop service");
    StartProcess("ssh", $"-p {sets.SshPort} {sets.SshAddress} cd {sets.HomePath}/server/; docker-compose rm -f service");
    StartProcess("scp", $"-P {sets.SshPort} ./artifacts/docker-compose.yml {sets.SshAddress}:{sets.HomePath}/server/");
    StartProcess("ssh", $"-p {sets.SshPort} {sets.SshAddress} cd {sets.HomePath}/server/; docker-compose create service");
    StartProcess("ssh", $"-p {sets.SshPort} {sets.SshAddress} cd {sets.HomePath}/server/; docker-compose start service");
});

// push new tag
Task("PushTagToGit")
    .Does(() =>
{
    StartProcess("git", $"tag v{version}");
    StartProcess("git", "push --tags");
});

// checkout tag
Task("CheckoutTag")
    .Does(() =>
{
    StartProcess("git", $"checkout tags/v{version}");
});

// checkout master
Task("CheckoutBranch")
    .Does(() =>
{
    StartProcess("git", $"checkout master");
});

// commit all to branch and push it
Task("Commit")
    .Does(() =>
{
    StartProcess("git", $"add .");
    StartProcess("git", $"commit -m \"Release v{version}\" ");
    StartProcess("git", $"push");
});

// check that we haven't uncommitted files
Task("CheckAllCommitted")
    .Does(() =>
{
    IEnumerable<string> redirectedOutput;
    StartProcess("git", 
        new ProcessSettings {
            Arguments = "status",
            RedirectStandardOutput = true
        },
        out redirectedOutput
    );
    if (!redirectedOutput.LastOrDefault().Contains("nothing to commit"))
    {
        throw new Exception("Exists uncommitted changes");
    }
});

RunTarget(target);

It is a script to build released version and publish a docker container to a server:

// Release: ./build.sh -t Release "-packageVersion=x.x.x.x"
// Publish: ./build.sh -t Publish "-packageVersion=x.x.x.x" "-env=counterra"
// Delete release: ./build.sh -t Delete "-packageVersion=x.x.x.x"
var target = Argument<string>("target");
var version = Argument<string>("packageVersion");

public class Settings
{
    public string SshAddress;
    public string SshPort;
    public string HomePath;
}

Settings sets;

Task("SelectEnvironment")
    .Does(()=>{
        var env = Argument<string>("env");
        var environments = new Dictionary<string, Settings> {
            ["counterra"] = new Settings {
                SshAddress = "root@83.220.171.23",
                SshPort = "22",
                HomePath = "/root/Templates"
            },
            ["tj"] = new Settings{
                SshAddress = "vorsa@195.14.96.218",
                SshPort = "26",
                HomePath = "/home/vorsa"
            }
        };
        sets = environments[env];
});

Task("Publish")
    .IsDependentOn("SelectEnvironment")
    .IsDependentOn("CheckAllCommitted")
    .IsDependentOn("CheckoutTag")
    // build angular
    .IsDependentOn("Build Angular")
    .IsDependentOn("BuildDocker")
    .IsDependentOn("CheckoutBranch")
    .IsDependentOn("PushToGitLab")
    .IsDependentOn("PublishService")
    .Does(()=>
    {
        Information("Finished!");
    });

Task("Release")
    .IsDependentOn("SetVersion")
    .IsDependentOn("ReleaseNotes")
    .IsDependentOn("Commit")
    .IsDependentOn("Build")
    .IsDependentOn("PushTagToGit")
    .Does(()=>
    {
        Information("Finished!");
    });

Task("Publish")
    // check that current 'master' branch hasn't uncommitted changes
    .IsDependentOn("CheckAllCommitted")
    // checkout release tag
    .IsDependentOn("CheckoutTag")
    // build angular
    .IsDependentOn("Build Angular")
    // build release 
    .IsDependentOn("Build")
    // return 'master' to HEAD as we have published release in artifacts folder
    .IsDependentOn("CheckoutBranch")
    // create docker image using published release
    .IsDependentOn("BuildDocker")
    // copy image to remote server
    .IsDependentOn("ExportDocker")
    // import new image, replace running container
    .IsDependentOn("PublishService")
    .Does(()=>
    {
        Information("Finished!");
    });

Task("Build Angular")
    .Does(() =>
{
    StartProcess("ng", "build");
});

Task("Build")
    .Does(() =>
{
    DotNetCoreRestore($ "./src/{Settings.ProjectName}");
    CleanDirectory("./artifacts");
    var settings = new DotNetCorePublishSettings
    {
        Framework = "netcoreapp1.1",
        Configuration = "Release",
        OutputDirectory = "./artifacts/"
    };
    DotNetCorePublish($ "./src/{Settings.ProjectName}", settings);
    // clean up artifacts folder
    DeleteDirectory("./artifacts/e2e", recursive:true);
    DeleteDirectory("./artifacts/src", recursive:true);
    DeleteFiles("./artifacts/ts*.json");
});

Task("BuildDocker")
    .Does(() =>
{
    var settings = new DockerBuildSettings {
        Tag = new []{ $ "{Settings.ContainerName}:{version}" }
    };
    DockerBuild(settings, ".");
});

Task("ExportDocker")
    .Does(() =>
{
    var settings = new DockerSaveSettings {
        Output = $ "./artifacts/{Settings.ContainerName}.{version}.tar"
    };
    // save docker image to tar archive
    DockerSave(settings, new[] { $ "{Settings.ContainerName}:{version}"});
});

Task("PublishService")
    .Does(() =>
{
    // cope docker image to remote server
    StartProcess("scp", $ "-P {Settings.SshPort} ./artifacts/{Settings.ContainerName}.{version}.tar {Settings.SshAddress}:{Settings.HomePath}/");
    // load copied image to docker on remote server
    StartProcess("ssh", $ "-p {Settings.SshPort} {Settings.SshAddress} docker load < {Settings.HomePath}/{Settings.ContainerName}.{version}.tar");

    // copy docker-compose configuration from remote server to artifacts folder
    StartProcess("scp", $ "-P {Settings.SshPort} {Settings.SshAddress}:{Settings.HomePath}/docker-compose.yml ./artifacts/");
    // replace the version of docker image in docker-compose configuration
    ReplaceRegexInFiles("./artifacts/docker-compose.yml", $ "{Settings.ContainerName}:\\d+\\.\\d+\\.\\d+\\.\\d+", $ "{Settings.ContainerName}:{version}");

    // stop old docker container on server
    StartProcess("ssh", $ "-p {Settings.SshPort} {Settings.SshAddress} cd {Settings.HomePath}/; docker-compose stop {Settings.ContainerName}");
    // delete old container
    StartProcess("ssh", $ "-p {Settings.SshPort} {Settings.SshAddress} cd {Settings.HomePath}/; docker-compose rm -f {Settings.ContainerName}");
    // copy new docker-compose configuration to remote server
    StartProcess("scp", $ "-P {Settings.SshPort} ./artifacts/docker-compose.yml {Settings.SshAddress}:{Settings.HomePath}/");
    // recreate docker container, it will create a new version
    StartProcess("ssh", $ "-p {Settings.SshPort} {Settings.SshAddress} cd {Settings.HomePath}/; docker-compose create {Settings.ContainerName}");
    // start new docker container
    StartProcess("ssh", $ "-p {Settings.SshPort} {Settings.SshAddress} cd {Settings.HomePath}/; docker-compose start {Settings.ContainerName}");
});

Task("CheckoutTag")
    .Does(() =>
{
    StartProcess("git", $ "checkout tags/v{version}");
});

Task("CheckoutBranch")
    .Does(() =>
{
    StartProcess("git", $ "checkout master");
});

Task("CheckAllCommitted")
    .Does(() =>
{
    IEnumerable<string> redirectedOutput;
    var exitCodeWithArgument = StartProcess("git", new ProcessSettings{
    Arguments = "status",
    RedirectStandardOutput = true
    },
    out redirectedOutput
    );
    if (!redirectedOutput.LastOrDefault().Contains("nothing to commit"))
    {
        throw new Exception("Exists uncommitted changes");
    }
});

RunTarget(target);

I am running service using docker compose like:

version: '2'
services:
    ContainerName:
        image: ContainerName:x.x.x.x

Thanks.